Current section
Files
Jump to
Current section
Files
lib/pg_case.ex
defmodule PgCase do
@moduledoc """
Helper macros to work with PostgreSQL `case` expression.
## Example
import Ecto.Query, only: [from: 2]
import PgCase, only: [pg_case: 1]
def query do
from e in Entity,
select: %{
value: pg_case do
e.x < 0 -> "negative"
e.x > 0 -> "positive"
else
"zero"
end
}
end
"""
@doc """
Adapts PostgreSQL `case` expression to elixir syntax.
The following construction
pg_case do
some_cond -> some_expr
some_other_cond -> some_other_expr
else
otherwise_expr
end
is equivalent to
CASE WHEN some_cond THEN some_expr
WHEN some_other_cond THEN some_other_expr
ELSE otherwise_expr
END
PostgreSQL expression.
Note that else-clause may be omitted. Then it works exactly like
`case` from PostgreSQL which returns `null` when none of
when-clauses matched.
"""
defmacro pg_case(body) do
conditions = body[:do]
else_clause = body[:else]
quote do
fragment(
unquote(
"CASE " <>
build_case_when_string(conditions) <>
build_case_else_string(else_clause) <>
"END"
),
unquote_splicing(
build_case_when_args(conditions) ++ build_case_else_args(else_clause)
)
)
end
end
defp build_case_when_string(conditions) do
Enum.reduce(conditions, "", fn {:->, _, _}, acc ->
acc <> "WHEN ? THEN ? "
end)
end
defp build_case_else_string(nil), do: ""
defp build_case_else_string(_), do: "ELSE ? "
defp build_case_when_args(conditions) do
Enum.flat_map(conditions, fn {:->, _, [[c], v]} -> [c, v] end)
end
defp build_case_else_args(nil), do: []
defp build_case_else_args(else_clause), do: [else_clause]
@doc """
Syntax sugar in case your use-case for `case` expression is simple
if-else construction.
pg_if some_cond do
some_expr
else
otherwise_expr
end
The code above is equivalent to following PosgreSQL expression:
CASE WHEN some_cond THEN some_expr
ELSE otherwise_expr
END
Note that else-clause may be omitted.
"""
defmacro pg_if(condition, body) do
do_clause =
body[:do] ||
raise SyntaxError,
description: "do-clause is required",
line: __CALLER__.line,
file: __CALLER__.file
else_clause = body[:else]
fragment_string =
"CASE WHEN ? THEN ? " <> build_if_else_string(else_clause) <> "END"
quote do
fragment(
unquote(fragment_string),
unquote(condition),
unquote_splicing([do_clause] ++ build_if_else_args(else_clause))
)
end
end
defp build_if_else_string(nil), do: ""
defp build_if_else_string(_), do: "ELSE ? "
defp build_if_else_args(nil), do: []
defp build_if_else_args(else_clause), do: [else_clause]
end