diff --git a/lib/feeb/db.ex b/lib/feeb/db.ex index cb09b80..3e6cb08 100644 --- a/lib/feeb/db.ex +++ b/lib/feeb/db.ex @@ -28,7 +28,7 @@ defmodule Feeb.DB do # We can't BEGIN EXCLUSIVE in a read-only database txn_type = if(access_type == :read, do: :deferred, else: transaction_type) - :ok = GenServer.call(get_pid!(), {:begin, txn_type}) + :ok = Repo.begin(get_pid!(), txn_type) end @doc """ @@ -58,9 +58,7 @@ defmodule Feeb.DB do It will also release the lock in the Repo connection, allowing other processes to grab it. """ def commit do - :ok = GenServer.call(get_pid!(), {:commit}) - # TODO: Close via repomanager - # :ok = GenServer.call(get_pid!(), {:close}) + :ok = Repo.commit(get_pid!()) delete_env() :ok end @@ -71,7 +69,7 @@ defmodule Feeb.DB do It will also release the lock in the Repo connection, allowing other processes to grab it. """ def rollback do - :ok = GenServer.call(get_pid!(), {:rollback}) + :ok = Repo.rollback(get_pid!()) delete_env() :ok end @@ -81,7 +79,7 @@ defmodule Feeb.DB do ################################################################################################## def raw(sql, bindings \\ []) do - GenServer.call(get_pid!(), {:raw, sql, bindings}) + Repo.raw(get_pid!(), sql, bindings) end def raw!(sql, bindings \\ []) do @@ -89,11 +87,6 @@ defmodule Feeb.DB do r end - def prepared_raw(sql, bindings, schema) do - opts = [schema: schema] - GenServer.call(get_pid!(), {:prepared_raw, sql, bindings, opts}) - end - def one(partial_or_full_query_id, bindings \\ [], opts \\ []) def one({domain, :fetch}, bindings, opts) when is_list(bindings) do @@ -115,7 +108,7 @@ defmodule Feeb.DB do def one({domain, query_name}, value, opts), do: one({domain, query_name}, [value], opts) def one({_, domain, query_name}, bindings, opts) when is_list(bindings) do - case GenServer.call(get_pid!(), {:query, :one, {domain, query_name}, bindings, opts}) do + case Repo.one(get_pid!(), {domain, query_name}, bindings, opts) do {:ok, r} -> r {:error, :multiple_results} -> raise "MultipleResultsError" end @@ -146,7 +139,7 @@ defmodule Feeb.DB do def all({domain, query_name}, value, opts), do: all({domain, query_name}, [value], opts) def all({_, domain, query_name}, bindings, opts) do - case GenServer.call(get_pid!(), {:query, :all, {domain, query_name}, bindings, opts}) do + case Repo.all(get_pid!(), {domain, query_name}, bindings, opts) do {:ok, rows} -> rows {:error, reason} -> raise reason end @@ -169,7 +162,7 @@ defmodule Feeb.DB do if struct.__meta__.valid? do bindings = get_bindings(full_query_id, struct) - GenServer.call(get_pid!(), {:query, :insert, {domain, query_name}, bindings, opts}) + Repo.insert(get_pid!(), {domain, query_name}, bindings, opts) else {:error, "Cast error: #{inspect(struct.__meta__.errors)}"} end @@ -191,7 +184,7 @@ defmodule Feeb.DB do true = :db == struct.__meta__.origin bindings = get_bindings(full_query_id, struct) - GenServer.call(get_pid!(), {:query, :update, {domain, query_name}, bindings, opts}) + Repo.update(get_pid!(), {domain, query_name}, bindings, opts) end def update_all(partial_or_full_query_id, bindings, opts \\ []) @@ -201,7 +194,7 @@ defmodule Feeb.DB do end def update_all({_, domain, query_name}, bindings, opts) do - GenServer.call(get_pid!(), {:query, :update_all, {domain, query_name}, bindings, opts}) + Repo.update_all(get_pid!(), {domain, query_name}, bindings, opts) end def update_all!(query_id, params, opts \\ []) do @@ -225,7 +218,7 @@ defmodule Feeb.DB do true = :db == struct.__meta__.origin bindings = get_bindings(full_query_id, struct) - GenServer.call(get_pid!(), {:query, :delete, {domain, query_name}, bindings, opts}) + Repo.delete(get_pid!(), {domain, query_name}, bindings, opts) end def delete_all(partial_or_full_query_id, bindings, opts \\ []) @@ -235,7 +228,7 @@ defmodule Feeb.DB do end def delete_all({_, domain, query_name}, bindings, opts) do - GenServer.call(get_pid!(), {:query, :delete_all, {domain, query_name}, bindings, opts}) + Repo.delete_all(get_pid!(), {domain, query_name}, bindings, opts) end def delete_all!(query_id, params, opts \\ []) do @@ -274,6 +267,7 @@ defmodule Feeb.DB do ################################################################################################## defp setup_env(context, shard_id, type, opts) when type in [:write, :read] do + # TODO: shard-aware telemetry {:ok, manager_pid} = Repo.Manager.Registry.fetch_or_create(context, shard_id) {:ok, repo_pid} = Repo.Manager.fetch_connection(manager_pid, type, opts) diff --git a/lib/feeb/db/application.ex b/lib/feeb/db/application.ex deleted file mode 100644 index b7cb9ac..0000000 --- a/lib/feeb/db/application.ex +++ /dev/null @@ -1,17 +0,0 @@ -defmodule Feeb.DB.Application do - @moduledoc false - use Application - - alias Feeb.DB.Repo - - def start(_type, _args) do - children = [ - Repo.Manager.Supervisor, - Repo.Manager.Registry, - {Task, fn -> Feeb.DB.Boot.run() end} - ] - - opts = [strategy: :one_for_one, name: Feeb.DB.Supervisor] - Supervisor.start_link(children, opts) - end -end diff --git a/lib/feeb/db/repo.ex b/lib/feeb/db/repo.ex index e49ce85..b2e096c 100644 --- a/lib/feeb/db/repo.ex +++ b/lib/feeb/db/repo.ex @@ -1,9 +1,6 @@ defmodule Feeb.DB.Repo do @moduledoc false - # This can be removed once the usage of `DB.prepared_raw/3` is consolidated - @dialyzer {:nowarn_function, format_custom: 3} - @struct_keys [:manager_pid, :context, :shard_id, :mode, :path, :conn, :transaction_id] @enforce_keys List.delete(@struct_keys, [:transaction_id]) defstruct @struct_keys @@ -25,6 +22,55 @@ defmodule Feeb.DB.Repo do def start_link({_, _, _, _, _} = args), do: GenServer.start_link(__MODULE__, args) + def begin(pid, txn_type) do + with_telemetry( + :begin, + fn -> + GenServer.call(pid, {:begin, txn_type, Logger.metadata()}) + end, + %{type: txn_type} + ) + end + + def commit(pid) do + with_telemetry(:commit, fn -> GenServer.call(pid, {:commit, Logger.metadata()}) end) + end + + def rollback(pid) do + with_telemetry(:rollback, fn -> + GenServer.call(pid, {:rollback, Logger.metadata()}) + end) + end + + def one(pid, {domain, query_name}, bindings, opts), + do: run_query(:one, pid, {domain, query_name}, bindings, opts) + + def all(pid, {domain, query_name}, bindings, opts), + do: run_query(:all, pid, {domain, query_name}, bindings, opts) + + def insert(pid, {domain, query_name}, bindings, opts), + do: run_query(:insert, pid, {domain, query_name}, bindings, opts) + + def update(pid, {domain, query_name}, bindings, opts), + do: run_query(:update, pid, {domain, query_name}, bindings, opts) + + def update_all(pid, {domain, query_name}, bindings, opts), + do: run_query(:update_all, pid, {domain, query_name}, bindings, opts) + + def delete(pid, {domain, query_name}, bindings, opts), + do: run_query(:delete, pid, {domain, query_name}, bindings, opts) + + def delete_all(pid, {domain, query_name}, bindings, opts), + do: run_query(:delete_all, pid, {domain, query_name}, bindings, opts) + + def raw(pid, sql, bindings) do + with_telemetry( + :query, + fn -> GenServer.call(pid, {:raw, sql, bindings}) end, + %{query_type: :raw} + ) + end + def close(pid), do: GenServer.call(pid, {:close}) @@ -35,9 +81,49 @@ defmodule Feeb.DB.Repo do def notify_release(pid), do: GenServer.call(pid, {:mgt_connection_released}) + defp run_query(query_type, pid, {domain, query_name}, bindings, opts) do + with_telemetry( + :query, + fn -> + GenServer.call( + pid, + {:query, query_type, {domain, query_name}, bindings, opts, Logger.metadata()} + ) + end, + %{query_type: query_type, domain: domain, query_name: query_name} + ) + end + + defp with_telemetry(name, cb, attrs \\ %{}) do + start = System.monotonic_time() + :telemetry.execute([:feebdb, name, :start], %{}, attrs) + + try do + result = cb.() + + :telemetry.execute( + [:feebdb, name, :stop], + %{duration: System.monotonic_time() - start}, + attrs + ) + + result + catch + kind, reason -> + :telemetry.execute( + [:feebdb, name, :exception], + %{duration: System.monotonic_time() - start}, + Map.merge(attrs, %{kind: kind, reason: reason, stacktrace: __STACKTRACE__}) + ) + + :erlang.raise(kind, reason, __STACKTRACE__) + end + end + # Server API def init({context, shard_id, path, mode, manager_pid}) do + Logger.metadata(context: context, shard_id: shard_id, path: path, mode: mode) Logger.info("Starting #{mode} repo for shard #{shard_id}@#{context}") true = mode in [:readwrite, :readonly] @@ -169,71 +255,91 @@ defmodule Feeb.DB.Repo do end # BEGIN - def handle_call({:begin, txn_type}, _from, %{transaction_id: nil} = state) do + def handle_call({:begin, txn_type, log_meta}, _from, %{transaction_id: nil} = state) do + start_custom_log_metadata_scope(log_meta) + Logger.debug("BEGIN") + {sql, _, _} = Query.fetch!({:begin, txn_type}) case SQLite.exec(state.conn, sql) do :ok -> txn_id = gen_transaction_id() + end_custom_log_metadata_scope() {:reply, :ok, %{state | transaction_id: txn_id}} {:error, r} -> + Logger.error("Unable to BEGIN: #{inspect(r)}") + end_custom_log_metadata_scope() {:reply, {:error, r}, state} end end - def handle_call({:begin, _type}, _from, state) do - Logger.error("Tried to BEGIN when already in a transaction") + def handle_call({:begin, _type, log_meta}, _from, state) do + Logger.error("Tried to BEGIN when already in a transaction", log_meta) {:reply, {:error, :already_in_transaction}, state} end # COMMIT - def handle_call({:commit}, _from, %{transaction_id: nil} = state) do - Logger.error("Tried to COMMIT when not in a transaction") + def handle_call({:commit, log_meta}, _from, %{transaction_id: nil} = state) do + Logger.error("Tried to COMMIT when not in a transaction", log_meta) {:reply, {:error, :not_in_transaction}, state} end - def handle_call({:commit}, _from, state) do + def handle_call({:commit, log_meta}, _from, state) do + start_custom_log_metadata_scope(log_meta) + sql = "COMMIT" + Logger.debug("COMMIT") case SQLite.exec(state.conn, sql) do :ok -> + end_custom_log_metadata_scope() {:reply, :ok, %{state | transaction_id: nil}} {:error, r} -> Logger.error("Error running #{sql}: #{inspect(r)}") + end_custom_log_metadata_scope() {:reply, {:error, r}, state} end end - # TODO: Test me # ROLLBACK - def handle_call({:rollback}, _from, %{transaction_id: nil} = state) do - Logger.error("Tried to ROLBACK when not in a transaction") + def handle_call({:rollback, log_meta}, _from, %{transaction_id: nil} = state) do + Logger.error("Tried to ROLBACK when not in a transaction", log_meta) {:reply, {:error, :not_in_transaction}, state} end - def handle_call({:rollback}, _from, state) do + def handle_call({:rollback, log_meta}, _from, state) do + start_custom_log_metadata_scope(log_meta) sql = "ROLLBACK" case SQLite.exec(state.conn, sql) do :ok -> + end_custom_log_metadata_scope() {:reply, :ok, %{state | transaction_id: nil}} {:error, r} -> Logger.error("Error running #{sql}: #{inspect(r)}") + end_custom_log_metadata_scope() {:reply, {:error, r}, state} end end # Queries (SELECT/UPDATE/INSERT/DELETE) - def handle_call({:query, type, {domain, query_name}, bindings_values, opts}, _from, state) do + def handle_call( + {:query, type, {domain, query_name}, bindings_values, opts, log_meta}, + _from, + state + ) do + start_custom_log_metadata_scope(log_meta) query_id = {state.context, domain, query_name} {sql, _, _} = query = Query.fetch!(query_id, opts) bindings_values = normalize_bindings_values(bindings_values) + Logger.debug("Query: #{inspect(sql)}. Bindings: #{inspect(bindings_values)}") + with {:ok, {stmt, stmt_sql}} <- prepare_query(state, query_id, sql), true = stmt_sql == sql, :ok <- SQLite.bind(stmt, bindings_values), @@ -244,29 +350,13 @@ defmodule Feeb.DB.Repo do } result = format_result(type, query_id, query, rows, bindings_values, attrs) + end_custom_log_metadata_scope() {:reply, result, state} else {:error, _} = err -> - Logger.error("error: #{inspect(err)}") - # TODO: Rollback? - {:reply, err, state} - end - end - - # TODO: Unused? At least make sure its usage is documented - def handle_call({:prepared_raw, raw_sql, bindings_values, opts}, _from, state) do - raise "Remove or document usage" - - with {:ok, stmt} <- SQLite.prepare(state.conn, raw_sql), - :ok <- SQLite.bind(stmt, bindings_values), - {:ok, rows} <- SQLite.all(state.conn, stmt) do - schema = Keyword.fetch!(opts, :schema) - result = format_custom(:all, schema, rows) - {:reply, result, state} - else - {:error, _} = err -> - Logger.error("error: #{inspect(err)}") + Logger.error("Query error: #{inspect(err)}") # TODO: Rollback? + end_custom_log_metadata_scope() {:reply, err, state} end end @@ -351,6 +441,7 @@ defmodule Feeb.DB.Repo do defp create_schema_from_rows(query_id, {_, {_, params_bindings}, :insert}, rows) do model = Schema.get_model_from_query_id(query_id) + Enum.map(rows, fn row -> Schema.from_row(model, params_bindings, row) end) end @@ -371,15 +462,6 @@ defmodule Feeb.DB.Repo do |> Enum.map(fn full_result -> Map.take(full_result, fields_bindings) end) end - # Used by `prepared_raw` exclusively - defp format_custom(:all, nil, rows) do - rows - end - - defp format_custom(:all, schema, rows) do - Enum.map(rows, fn row -> Schema.from_row(schema, [:*], row) end) - end - defp prepare_query(state, _query_id, sql) do # NOTE: On Feeb.DB.Connection I'm using ETS to cache stmt. Measure it. with {:ok, stmt} <- SQLite.prepare(state.conn, sql) do @@ -435,4 +517,15 @@ defmodule Feeb.DB.Repo do "Repo can only be released by its Manager (#{inspect(manager_pid)}, got #{inspect(other_pid)})" |> raise() end + + # Logger metadata handling + + defp start_custom_log_metadata_scope(custom_metadata) do + Process.put({:feebdb, :original_scope_metadata}, Logger.metadata()) + Logger.metadata(custom_metadata) + end + + defp end_custom_log_metadata_scope do + Logger.reset_metadata(Process.get({:feebdb, :original_scope_metadata})) + end end diff --git a/lib/feeb/db/repo/manager.ex b/lib/feeb/db/repo/manager.ex index aadd9e8..61e77c6 100644 --- a/lib/feeb/db/repo/manager.ex +++ b/lib/feeb/db/repo/manager.ex @@ -81,6 +81,8 @@ defmodule Feeb.DB.Repo.Manager do # Server API def init({context, shard_id}) do + Logger.metadata(context: context, shard_id: shard_id) + Logger.info("Starting repo manager for shard #{shard_id} #{inspect(self())}") state = %__MODULE__{ @@ -246,12 +248,12 @@ defmodule Feeb.DB.Repo.Manager do {:ok, key} -> repo_entry = fetch_repo_entry!(state, key) - # Stop monitoring the caller process since it released the connection - Process.demonitor(repo_entry.monitor_ref) - # Stop the repo_timeout timer stop_timer(repo_entry.timer_ref) + # Stop monitoring the caller process since it released the connection + Process.demonitor(repo_entry.monitor_ref) + # Notify the Repo that it's been released :ok = Repo.notify_release(pid) diff --git a/lib/feeb/db/repo/manager/repo_entry.ex b/lib/feeb/db/repo/manager/repo_entry.ex index 4c4a8ad..073cdc1 100644 --- a/lib/feeb/db/repo/manager/repo_entry.ex +++ b/lib/feeb/db/repo/manager/repo_entry.ex @@ -1,6 +1,6 @@ defmodule Feeb.DB.Repo.Manager.RepoEntry do @enforce_keys [:busy?] - defstruct [:pid, :caller_pid, :timer_ref, :monitor_ref, busy?: false] + defstruct [:pid, :caller_pid, :time, :timer_ref, :monitor_ref, busy?: false] @doc """ Creates an initial RepoEntry @@ -20,6 +20,7 @@ defmodule Feeb.DB.Repo.Manager.RepoEntry do %__MODULE__{ pid: pid, busy?: true, + time: System.system_time(:millisecond), caller_pid: caller_pid, monitor_ref: monitor_ref, timer_ref: timer_ref diff --git a/lib/feeb/db/sqlite.ex b/lib/feeb/db/sqlite.ex index 6f647b0..c5af856 100644 --- a/lib/feeb/db/sqlite.ex +++ b/lib/feeb/db/sqlite.ex @@ -13,11 +13,11 @@ defmodule Feeb.DB.SQLite do @default_chunk_size 50 - def open(path) when is_binary(path), do: Driver.open(path) + def open(path) when is_binary(path), do: traced_driver_operation(:open, [path]) - def close(conn), do: Driver.close(conn) + def close(conn), do: traced_driver_operation(:close, [conn]) - def exec(conn, sql), do: Driver.execute(conn, sql) + def exec(conn, sql), do: traced_driver_operation(:execute, [conn, sql]) def raw(conn, sql) do with {:ok, stmt} <- prepare(conn, sql) do @@ -35,7 +35,7 @@ defmodule Feeb.DB.SQLite do # TODO: What is the error type? Add typespecs and review Repo code def prepare(conn, sql) do - Driver.prepare(conn, sql) + traced_driver_operation(:prepare, [conn, sql]) end def bind(_, _, []), @@ -43,7 +43,7 @@ defmodule Feeb.DB.SQLite do def bind(stmt, bindings) when is_list(bindings) do try do - Driver.bind(stmt, bindings) + traced_driver_operation(:bind, [stmt, bindings]) rescue e in ArgumentError -> Logger.error(e) @@ -83,10 +83,16 @@ defmodule Feeb.DB.SQLite do end defp step_chunk(conn, stmt, chunk_size) do - case Driver.multi_step(conn, stmt, chunk_size) do + case traced_driver_operation(:multi_step, [conn, stmt, chunk_size]) do {:rows, rows} -> {:ok, :not_done, rows} {:done, rows} -> {:ok, :done, rows} {:error, _} = error -> error end end + + defp traced_driver_operation(operation, args) do + {duration, result} = :timer.tc(fn -> apply(Driver, operation, args) end) + :telemetry.execute([:feebdb, :driver_op, operation], %{duration: duration}, %{}) + result + end end diff --git a/lib/feeb/db/supervisor.ex b/lib/feeb/db/supervisor.ex new file mode 100644 index 0000000..2176c67 --- /dev/null +++ b/lib/feeb/db/supervisor.ex @@ -0,0 +1,20 @@ +defmodule Feeb.DB.Supervisor do + @moduledoc false + use Supervisor + + alias Feeb.DB.Repo + + def start_link(_) do + Supervisor.start_link(__MODULE__, [], name: __MODULE__) + end + + def init(_) do + children = [ + Repo.Manager.Supervisor, + Repo.Manager.Registry, + {Task, fn -> Feeb.DB.Boot.run() end} + ] + + Supervisor.init(children, strategy: :one_for_one) + end +end diff --git a/mix.exs b/mix.exs index c618d2b..82d6214 100644 --- a/mix.exs +++ b/mix.exs @@ -30,7 +30,7 @@ defmodule Feeb.DB.Mixfile do def application do [ env: [], - mod: {Feeb.DB.Application, []} + mod: [] ] end @@ -56,6 +56,7 @@ defmodule Feeb.DB.Mixfile do [ {:exqlite, "~> 0.33"}, {:stack, "~> 1.0"}, + {:telemetry, "~> 1.3"}, {:dialyxir, "~> 1.4", only: [:dev], runtime: false}, {:excoveralls, "~> 0.18.5", only: :test}, {:mix_test_watch, "~> 1.2", only: [:dev, :test], runtime: false}, diff --git a/test/db/repo/manager_test.exs b/test/db/repo/manager_test.exs index c59ef2c..2fd4fe7 100644 --- a/test/db/repo/manager_test.exs +++ b/test/db/repo/manager_test.exs @@ -1,7 +1,7 @@ defmodule Feeb.DB.Repo.ManagerTest do use Test.Feeb.DBCase, async: true - alias Feeb.DB.{Config} + alias Feeb.DB.{Config, Repo} alias Feeb.DB.Repo.Manager @context :test @@ -331,7 +331,7 @@ defmodule Feeb.DB.Repo.ManagerTest do assert {:ok, repo} = Manager.fetch_connection(manager, :write, timeout: 25) # The leasee actually started a DB transaction - assert :ok == GenServer.call(repo, {:begin, :exclusive}) + assert :ok == Repo.begin(repo, :exclusive) # And there is a `transaction_id` reference in the Repo state repo_state = :sys.get_state(repo) diff --git a/test/db/repo_test.exs b/test/db/repo_test.exs index 27b965a..294cf53 100644 --- a/test/db/repo_test.exs +++ b/test/db/repo_test.exs @@ -1,7 +1,6 @@ defmodule Feeb.DB.RepoTest do use Test.Feeb.DBCase, async: true - alias GenServer, as: GS alias Feeb.DB.{Config, Repo, SQLite} @context :test @@ -87,11 +86,11 @@ defmodule Feeb.DB.RepoTest do refute :sys.get_state(repo).transaction_id # Now we are in a transaction - assert :ok == GS.call(repo, {:begin, :exclusive}) + assert :ok == Repo.begin(repo, :exclusive) assert :sys.get_state(repo).transaction_id # No longer in a transaction once we commit - assert :ok == GS.call(repo, {:commit}) + assert :ok == Repo.commit(repo) refute :sys.get_state(repo).transaction_id # Proof that we are no longer in a transaction: rollback won't work @@ -101,10 +100,9 @@ defmodule Feeb.DB.RepoTest do @tag capture_log: true test "can't BEGIN twice", %{repo: repo} do - assert :ok == GS.call(repo, {:begin, :deferred}) + assert :ok == Repo.begin(repo, :deferred) - assert {:error, :already_in_transaction} == - GS.call(repo, {:begin, :exclusive}) + assert {:error, :already_in_transaction} == Repo.begin(repo, :exclusive) # Proof that we are in a transaction: we can rollback (only once, of course) c = :sys.get_state(repo).conn @@ -114,11 +112,11 @@ defmodule Feeb.DB.RepoTest do @tag capture_log: true test "can't COMMIT when not in a transaction", %{repo: repo} do - assert {:error, :not_in_transaction} == GS.call(repo, {:commit}) + assert {:error, :not_in_transaction} == Repo.commit(repo) - assert :ok == GS.call(repo, {:begin, :deferred}) - assert :ok == GS.call(repo, {:commit}) - assert {:error, :not_in_transaction} == GS.call(repo, {:commit}) + assert :ok == Repo.begin(repo, :deferred) + assert :ok == Repo.commit(repo) + assert {:error, :not_in_transaction} == Repo.commit(repo) end end @@ -126,33 +124,31 @@ defmodule Feeb.DB.RepoTest do test "returns the corresponding result", %{repo: repo} do q = {:friends, :get_by_id} - assert {:ok, %{id: 1, name: "Phoebe"}} = GS.call(repo, {:query, :one, q, [1], []}) + assert {:ok, %{id: 1, name: "Phoebe"}} = Repo.one(repo, q, [1], []) - assert {:ok, nil} == GS.call(repo, {:query, :one, q, [9], []}) + assert {:ok, nil} == Repo.one(repo, q, [9], []) end @tag capture_log: true test "handles errors", %{repo: repo} do # Multiple results being returned at once - assert {:error, :multiple_results} = - GS.call(repo, {:query, :one, {:friends, :get_all}, [], []}) + assert {:error, :multiple_results} = Repo.one(repo, {:friends, :get_all}, [], []) # Wrong number of bindings - assert {:error, :arguments_wrong_length} = - GS.call(repo, {:query, :one, {:friends, :get_by_id}, [1, 2], []}) + assert {:error, :arguments_wrong_length} = Repo.one(repo, {:friends, :get_by_id}, [1, 2], []) end end describe "handle_call: raw" do @tag capture_log: true test "executes raw queries", %{repo: repo} do - assert {:ok, rows} = GS.call(repo, {:raw, "select * from friends", []}) + assert {:ok, rows} = Repo.raw(repo, "select * from friends", []) assert length(rows) == 6 # Now with bindings - assert {:ok, _} = GS.call(repo, {:raw, "delete from friends where id = ?", [1]}) + assert {:ok, _} = Repo.raw(repo, "delete from friends where id = ?", [1]) - assert {:ok, rows} = GS.call(repo, {:raw, "select * from friends", []}) + assert {:ok, rows} = Repo.raw(repo, "select * from friends", []) assert length(rows) == 5 end @@ -161,7 +157,7 @@ defmodule Feeb.DB.RepoTest do log = capture_log(fn -> - assert {:ok, _} = GS.call(repo, {:raw, "select * from friends", []}) + assert {:ok, _} = Repo.raw(repo, "select * from friends", []) end) assert log =~ "warning" @@ -171,32 +167,30 @@ defmodule Feeb.DB.RepoTest do describe "handle_call: close" do test "closes the connection and kills the server", %{repo: repo} do - assert :ok == GS.call(repo, {:close}) + assert :ok == Repo.close(repo) # Process is dead refute Process.alive?(repo) end test "can't close the connection while in a transaction", %{repo: repo} do # We are in a transaction - assert :ok == GS.call(repo, {:begin, :exclusive}) + assert :ok == Repo.begin(repo, :exclusive) assert :sys.get_state(repo).transaction_id # Can't close this log = capture_log(fn -> - assert {:error, :cant_close_with_transaction} == - GS.call(repo, {:close}) - + assert {:error, :cant_close_with_transaction} == Repo.close(repo) assert Process.alive?(repo) end) assert log =~ "[error] Tried to close a Repo while in a transaction" # But once we commit... - assert :ok == GS.call(repo, {:commit}) + assert :ok == Repo.commit(repo) # We can close it - assert :ok == GS.call(repo, {:close}) + assert :ok == Repo.close(repo) refute Process.alive?(repo) end end @@ -204,15 +198,15 @@ defmodule Feeb.DB.RepoTest do describe "handle_call: mgt_connection_released" do test "performs a no-op on the regular lifecycle", %{repo: repo} do # This Repo had a regular lifecycle: it started a transaction - assert :ok == GS.call(repo, {:begin, :exclusive}) + assert :ok == Repo.begin(repo, :exclusive) assert :sys.get_state(repo).transaction_id # And then it committed, which resets the transaction_id - assert :ok == GS.call(repo, {:commit}) + assert :ok == Repo.commit(repo) state_after_commit = :sys.get_state(repo) refute state_after_commit.transaction_id - assert :ok == GS.call(repo, {:mgt_connection_released}) + assert :ok == Repo.notify_release(repo) state_after_release = :sys.get_state(repo) # Nothing changes when the connection is released @@ -221,12 +215,12 @@ defmodule Feeb.DB.RepoTest do test "rolls back transaction if one exists", %{repo: repo} do # We are in a transaction - assert :ok == GS.call(repo, {:begin, :exclusive}) + assert :ok == Repo.begin(repo, :exclusive) assert :sys.get_state(repo).transaction_id # Let's assume mgt_connection_released was called with an active transaction -- that means the # caller was forced to end its connection (e.g. due to timeout or caller dying) - assert :ok == GS.call(repo, {:mgt_connection_released}) + assert :ok == Repo.notify_release(repo) refute :sys.get_state(repo).transaction_id # If we try to ROLLBACK, SQLite yells at us saying there's no open transaction @@ -239,11 +233,11 @@ defmodule Feeb.DB.RepoTest do assert {:ok, repo} = Repo.start_link({@context, shard_id, db, :readwrite, self()}) # It works if called from the same process (here, test process == manager process) - assert :ok == GS.call(repo, {:mgt_connection_released}) + assert :ok == Repo.notify_release(repo) # It blows up if called from elsewhere spawn(fn -> - GS.call(repo, {:mgt_connection_released}) + Repo.notify_release(repo) block_forever() end) diff --git a/test/test_helper.exs b/test/test_helper.exs index c1e7366..40e310a 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,3 +1,5 @@ +Feeb.DB.Supervisor.start_link([]) + Test.Feeb.DB.on_start() ExUnit.start()