Skip to content

Commit a646203

Browse files
committed
Add SQL.map/2 and impl enumerable
1 parent 5bd7ec3 commit a646203

File tree

3 files changed

+80
-1
lines changed

3 files changed

+80
-1
lines changed

.iex.exs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33

44
defmodule SQL.Repo do
55
use Ecto.Repo, otp_app: :sql, adapter: Ecto.Adapters.Postgres
6+
use SQL, adapter: SQL.Adapters.Postgres, repo: __MODULE__
7+
8+
def list_ids() do
9+
~SQL[select 1 as id]
10+
|> SQL.map(fn row, columns, repo -> repo.load(%{id: :integer}, {columns, row}) end)
11+
|> SQL.map(fn row -> row.id end)
12+
|> Enum.map(fn v -> v end)
13+
end
614
end
715
Application.put_env(:sql, :driver, Postgrex)
816
Application.put_env(:sql, :username, "postgres")

lib/sql.ex

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ defmodule SQL do
2323
end
2424
end
2525

26-
defstruct [tokens: [], params: [], module: nil, id: nil, string: nil, inspect: nil]
26+
defstruct [tokens: [], params: [], module: nil, id: nil, string: nil, inspect: nil, fn: nil]
2727

2828
defimpl Inspect, for: SQL do
2929
def inspect(sql, _opts), do: sql.inspect
@@ -64,6 +64,24 @@ defmodule SQL do
6464
SQL.build(left, right, modifiers, __CALLER__)
6565
end
6666

67+
@doc """
68+
Perform transformation on the result set.
69+
70+
## Examples
71+
iex(1)> SQL.map(~SQL"from users select id, email", fn row, columns -> Map.new(Enum.zip(columns, row)) end)
72+
~SQL\"\"\"
73+
select
74+
id,
75+
email
76+
from
77+
users
78+
\"\"\"
79+
"""
80+
@doc since: "0.4.0"
81+
defmacro map(left, right) do
82+
SQL.build(left, right, __CALLER__)
83+
end
84+
6785
@doc false
6886
@doc since: "0.1.0"
6987
def parse(binary, opts \\ [format: true, module: ANSI, sql_lock: nil]) do
@@ -114,6 +132,23 @@ defmodule SQL do
114132
end
115133
end
116134

135+
@doc false
136+
def build(left, {tag, _, _} = right, _env) when tag in ~w[fn &]a do
137+
{_type, data, acc2} = left
138+
|> Macro.unpipe()
139+
|> Enum.reduce({:static, [], []}, fn
140+
{[], 0}, acc -> acc
141+
{{_, _, []} = r, 0}, {_, l, right} -> {:dynamic, Macro.pipe(l, r, 0), right}
142+
{{:sigil_SQL, _meta, [{:<<>>, _, _}, []]} = r, 0}, {type, l, right} -> {type, Macro.pipe(l, r, 0), right}
143+
{{{:.,_,[{_,_,[:SQL]},:map]},_,[left]}, 0}, {type, acc, acc2} -> {type, acc, [left|acc2]}
144+
end)
145+
[r | rest] = Enum.reverse([right|acc2])
146+
right = Enum.reduce(rest, r, fn r, {t, m, [{t2, m2, [vars, block]}]} -> {t, m, [{t2, m2, [vars, quote(do: unquote(r).(unquote(block)))]}]} end)
147+
quote bind_quoted: [left: data, right: right] do
148+
%{left | fn: right}
149+
end
150+
end
151+
117152
@doc false
118153
def build(left, {:<<>>, _, right}) do
119154
left
@@ -244,4 +279,26 @@ defmodule SQL do
244279
IO.warn(IO.ANSI.format([?\n, format_error(errors), iodata]), [stack|t])
245280
{IO.iodata_to_binary(module.token_to_string(tokens)), ~s[~SQL"""\n#{IO.iodata_to_binary(IO.ANSI.format(iodata, false))}"""]}
246281
end
282+
283+
defimpl Enumerable, for: SQL do
284+
def count(_enumerable) do
285+
{:error, __MODULE__}
286+
end
287+
def member?(_enumerable, _element) do
288+
{:error, __MODULE__}
289+
end
290+
def reduce(%SQL{} = enumerable, acc, fun) do
291+
repo = enumerable.module.sql_config()[:repo]
292+
%{rows: rows, columns: columns} = repo.query!(enumerable.string, enumerable.params)
293+
fun = case Function.info(enumerable.fn, :arity) do
294+
{:arity, 1} -> fn row, acc -> fun.(enumerable.fn.(row), acc) end
295+
{:arity, 2} -> fn row, acc -> fun.(enumerable.fn.(row, columns), acc) end
296+
{:arity, 3} -> fn row, acc -> fun.(enumerable.fn.(row, columns, repo), acc) end
297+
end
298+
Enumerable.reduce(rows, acc, fun)
299+
end
300+
def slice(_enumerable) do
301+
{:error, __MODULE__}
302+
end
303+
end
247304
end

test/sql_test.exs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,20 @@ defmodule SQLTest do
3434
end
3535
end
3636

37+
test "map/2" do
38+
columns = ~w[id email inserted_at updated_at]a
39+
sql =
40+
from()
41+
|> ~SQL[select id, email, inserted_at, updated_at]
42+
|> where()
43+
|> SQL.map(fn row -> row end)
44+
|> SQL.map(fn row -> Map.new(Enum.zip(columns, row)) end)
45+
|> SQL.map(&(&1.id))
46+
47+
assert ["id"] == Enum.map([["id", "email", "inserted_at", "updated_at"]], fn row -> sql.fn.(row) end)
48+
end
49+
50+
3751
test "inspect/1" do
3852
assert ~s(~SQL"""\nselect\n +1000\n""") == inspect(~SQL[select +1000])
3953
end

0 commit comments

Comments
 (0)