defmodule Qlc do defmodule Cursor do defstruct c: nil end @type bindings :: [any] @type query_cursor :: Qlc.Cursor @type expr :: any @type qlc_opt :: record(:qlc_opt) @type qlc_handle :: record(:qlc_handle) @type qlc_lc :: record(:qlc_lc) require Record @qlc_handle_fields Record.extract(:qlc_handle, from_lib: "stdlib/src/qlc.erl") Record.defrecord :qlc_handle, @qlc_handle_fields @qlc_opt_fields Record.extract(:qlc_opt, from_lib: "stdlib/src/qlc.erl") Record.defrecord :qlc_opt, @qlc_opt_fields @qlc_lc_fields Record.extract(:qlc_lc, from_lib: "stdlib/src/qlc.erl") Record.defrecord :qlc_lc, @qlc_lc_fields @doc """ string to erlang ast """ @spec exprs(String.t) :: expr def exprs(s) do c = String.to_char_list(s) {:ok, m, _} = :erl_scan.string(c) {:ok, [expr]} = :erl_parse.parse_exprs(m) expr end @doc """ optoin list to record(:qlc_opt) """ @spec options(list, list, qlc_opt) :: qlc_opt def options(_opt, [], acc) do acc end @spec options(list, list, qlc_opt) :: qlc_opt def options(opt, [k|l], acc) do acc = case Keyword.get(opt, k, nil) do nil -> if Enum.member?(opt, k) do case k do :unique -> qlc_opt(acc, unique: true) :cache -> qlc_opt(acc, cache: true) end else acc end r -> case k do :max_lookup -> qlc_opt(acc, max_lookup: r) :cache -> qlc_opt(acc, cache: r) :join -> qlc_opt(acc, join: r) :unique -> qlc_opt(acc, unique: r) end end options(opt, l, acc) end @doc """ erlang ast with binding variables to qlc_handle """ def expr_to_handle(expr, bind, opt) do {:ok, {:call, _, _q, handle}} = :qlc_pt.transform_expression(expr, bind) {:value, q, _} = :erl_eval.exprs(handle, bind) optkeys = [:max_lookup,:cache, :join,:lookup,:unique] opt_r = options(opt, optkeys, qlc_opt()) qlc_handle(h: qlc_lc(q, opt: opt_r)) end @doc """ variable binding list to erlang_binding list """ @spec bind([], bindings) :: bindings def bind([], b) do b end @spec bind([Keyword], bindings) :: bindings def bind([{k, v} | t], b) when is_atom(k) do bind(t, :erl_eval.add_binding(k, v, b)) end @spec bind([Keyword]) :: bindings def bind(a) when is_list(a) do bind(a, :erl_eval.new_binding()) end @doc """ string to qlc_handle with variable bindings """ @spec string_to_handle(String.t, bindings, list) :: qlc_handle def string_to_handle(s, bindings, opt \\ []) when is_binary(s) do :qlc.string_to_handle(String.to_char_list(s), bindings, opt) end @doc """ string to qlc_handle with variable bindings (string must be literal) qlc expression string syntax [expression || qualifier1, qualifier2, ...] expression :: arbitary Erlang term (the template) qualifier :: filter or generators filter :: Erlang expressions returning bool() generator :: Pattern <- ListExpression ListExpression :: qlc_handle or list qlc_handle :: returned from Qlc.table/2, Qlc.sort/2, Qlc.keysort/2 Qlc.q/2, Qlc.string_to_handle/2 """ @spec q(String.t, bindings, list) :: qlc_handle defmacro q(string, bindings, opt \\ []) when is_binary(string) do string = Regex.replace(~r/([^\.])$/, string, "\\1.") expr = exprs(string) exprl = Macro.escape(expr) quote bind_quoted: [exprl: exprl, bindings: bindings, opt: opt] do Qlc.expr_to_handle(exprl, bindings, opt) end end @doc """ eval qlc_handle """ @spec e(qlc_handle) :: list def e(qh) do :qlc.e(qh) end @doc """ fold qlc_handle with accumulator """ @spec fold(qlc_handle, any, (any, any -> any), [any]) :: any def fold(qh, a, f, option \\ []) do :qlc.fold(f, a, qh, option) end @doc """ create qlc cursor from qlc_handle (create processes) """ @spec cursor(qlc_handle) :: query_cursor def cursor(qh) do %Qlc.Cursor{c: :qlc.cursor(qh)} end @doc """ delete qlc cursor (kill processes) """ @spec delete_cursor(Qlc.Cursor) :: :ok def delete_cursor(qc) do :qlc.delete_cursor(qc.c) end end defimpl Enumerable, for: Qlc.Cursor do def count(_qc) do {:error, __MODULE__} end def member?(_qc,_) do {:error, __MODULE__} end def reduce(qc, {:halt, acc}, _fun) do Qlc.delete_cursor(qc) {:halted, acc} end def reduce(qc, {:suspend, acc}, fun) do {:suspended, acc, fn(x) -> reduce(qc, x, fun) end} end def reduce(qc, {:cont, acc}, fun) do case :qlc.next_answers(qc.c, 1) do [] -> Qlc.delete_cursor(qc) {:done, acc} [h] -> reduce(qc, fun.(h, acc), fun) end end end