Current section
Files
Jump to
Current section
Files
lib/ecto_mysql_extras.ex
defmodule EctoMySQLExtras do
@moduledoc """
Documentation for `EctoMySQLExtras`.
"""
@callback info :: %{
required(:title) => String.t(),
required(:columns) => [%{name: atom(), type: atom()}],
optional(:order_by) => [{atom(), :ASC | :DESC}],
optional(:args) => [atom()]
}
@type repo() :: module() | {module(), node()}
@spec queries(repo()) :: map()
def queries(_repo \\ nil) do
%{
index_size: EctoMySQLExtras.IndexSize,
plugins: EctoMySQLExtras.Plugins,
table_indexes_size: EctoMySQLExtras.TableIndexesSize,
table_size: EctoMySQLExtras.TableSize,
total_index_size: EctoMySQLExtras.TotalIndexSize,
total_table_size: EctoMySQLExtras.TotalTableSize
}
end
@spec query(atom(), repo(), keyword()) :: :ok | MyXQL.Result.t()
def query(query_name, repo, opts \\ []) do
query_module = Map.fetch!(queries(), query_name)
opts = default_opts(opts)
result = query!(repo, query_module.query(opts))
format(
Keyword.fetch!(opts, :format),
query_module.info(),
result
)
end
defp query!({repo, node}, query) do
case :rpc.call(node, repo, :query!, [query]) do
{:badrpc, {:EXIT, {:undef, _}}} ->
raise "repository is not defined on remote node"
{:badrpc, error} ->
raise "cannot send query to remote node #{inspect(node)}. Reason: #{inspect(error)}"
result ->
result
end
end
defp query!(repo, query) do
repo.query!(query)
end
@spec index_size(atom(), keyword()) :: :ok | MyXQL.Result.t()
def index_size(repo, opts \\ []), do: query(:index_size, repo, opts)
@spec plugins(atom(), keyword()) :: :ok | MyXQL.Result.t()
def plugins(repo, opts \\ []), do: query(:plugins, repo, opts)
@spec table_indexes_size(atom(), keyword()) :: :ok | MyXQL.Result.t()
def table_indexes_size(repo, opts \\ []), do: query(:table_indexes_size, repo, opts)
@spec table_size(atom(), keyword()) :: :ok | MyXQL.Result.t()
def table_size(repo, opts \\ []), do: query(:table_size, repo, opts)
@spec total_index_size(atom(), keyword()) :: :ok | MyXQL.Result.t()
def total_index_size(repo, opts \\ []), do: query(:total_index_size, repo, opts)
@spec total_table_size(atom(), keyword()) :: :ok | MyXQL.Result.t()
def total_table_size(repo, opts \\ []), do: query(:total_table_size, repo, opts)
defp default_opts(opts) do
default = [format: :raw]
Keyword.merge(default, opts)
end
defp format(:raw, _info, result), do: result
if Code.ensure_loaded?(TableRex) do
defp format(:ascii, info, result) do
names = Enum.map(info.columns, & &1.name)
types = Enum.map(info.columns, & &1.type)
rows =
if result.rows == [] do
[["No results", nil]]
else
Enum.map(result.rows, &parse_row(&1, types))
end
rows
|> TableRex.quick_render!(names, info.title)
|> IO.puts()
end
defp parse_row(list, types) do
list
|> Enum.zip(types)
|> Enum.map(&format_value/1)
end
def format_value({integer, :bytes}) when is_integer(integer), do: format_bytes(integer)
def format_value({string, :string}), do: String.replace(string, "\n", "")
def format_value({other, _}), do: inspect(other)
defp format_bytes(bytes) do
cond do
bytes >= memory_unit(:TB) -> format_bytes(bytes, :TB)
bytes >= memory_unit(:GB) -> format_bytes(bytes, :GB)
bytes >= memory_unit(:MB) -> format_bytes(bytes, :MB)
bytes >= memory_unit(:KB) -> format_bytes(bytes, :KB)
true -> format_bytes(bytes, :B)
end
end
defp format_bytes(bytes, :B) when is_integer(bytes), do: "#{bytes} bytes"
defp format_bytes(bytes, unit) when is_integer(bytes) do
value = bytes / memory_unit(unit)
"#{:erlang.float_to_binary(value, decimals: 1)} #{unit}"
end
defp memory_unit(:TB), do: 1024 * 1024 * 1024 * 1024
defp memory_unit(:GB), do: 1024 * 1024 * 1024
defp memory_unit(:MB), do: 1024 * 1024
defp memory_unit(:KB), do: 1024
else
defp format(:ascii, _info, _result) do
IO.warn("""
If you want to display query results in ASCII format you should add `table_rex` as a dependency.
""")
end
end
end