Current section
Files
Jump to
Current section
Files
lib/tiny_ecto_helper_mysql.ex
defmodule TinyEctoHelperMySQL do
@moduledoc """
Documentation for TinyEctoHelperMySQL
"""
@doc """
get columns lists in queryable
from(
q in Question,
select: {q.id, q.title, q.body}
) |> TinyEctoHelperMySQL.get_select_keys()
|
|
|
[:id, :title, :body]
"""
@error_not_queryable {:error, :not_queryable}
@error_no_select {:error, :not_found_select}
def get_select_keys(query) when is_map(query) do
case get_select(query) do
{:error, msg} ->
{:error, msg}
{:ok, select} ->
{_, _, expr} = select.expr
case expr do
[] ->
@error_no_select
expr ->
keys =
expr
|> Enum.map(fn x ->
{{_, _, [_, key]}, _, _} = x
key
end)
{:ok, keys}
end
end
end
def get_select_keys(_), do: get_select_keys()
def get_select_keys() do
@error_not_queryable
end
@doc """
## Examples
Issue query to MySQL with queryable AND `SELECT SQL_CALC_FOUND_ROWS`, then return the result and the count returned from `SELECT FOUND_ROWS()` like following:
query =
from(
q in Question,
select: {q.id, q.title, q.body, q.user_id, q.inserted_at, q.updated_at},
order_by: [asc: q.id]
)
{:ok, select_keys} = TinyEctoHelperMySQL.get_select_keys(query)
TinyEctoHelperMySQL.query_and_found_rows(query, select_keys, [Repo, %Question{}, Question])
|
|
|
{:ok, %{results: results, count: count}}
"""
def query_and_found_rows(query, keys, [repo, struct, model]) do
{:ok, [results, count]} =
repo.transaction(fn ->
[inject_sql, values] = build_inject_sql(repo, query)
results =
Ecto.Adapters.SQL.query!(repo, inject_sql, values).rows
|> merge_result_keys(keys, [struct, model])
[[count]] = Ecto.Adapters.SQL.query!(repo, "SELECT FOUND_ROWS()").rows
[results, count]
end)
{:ok, %{results: results, count: count}}
end
defp get_select(query) when is_map(query) do
if not Map.has_key?(query, :__struct__) do
@error_not_queryable
else
select = Map.get(query, :select)
# select exists, but it may be nil(e.g. select: {})
if select == %{} or is_nil(select) do
@error_no_select
else
{:ok, select}
end
end
end
defp get_select(_), do: @error_not_queryable
defp build_inject_sql(repo, query) do
{sql, values} = repo.to_sql(:all, query)
inject_sql = String.replace_prefix(sql, "SELECT ", "SELECT SQL_CALC_FOUND_ROWS ")
[inject_sql, values]
end
# build maps such as {id: 1, title: "xxxx"}
defp merge_result_keys(raw_result, keys, [struct, model]) do
raw_result
|> Enum.map(fn x ->
key =
Enum.zip(keys, x)
|> Enum.reduce(%{}, fn {key, value}, acc ->
Map.put(acc, key, value)
end)
# call validation just in case.
true = model.changeset(struct, key).valid?
Map.merge(struct, key)
end)
end
end