Current section
Files
Jump to
Current section
Files
lib/qlc.ex
defmodule Qlc do
@type binding_struct :: :erl_eval.binding_struct()
@type bindings :: Keyword.t
@type query_cursor :: Qlc.Cursor.t
@type abstract_expr :: :erl_parse.abstract_expr()
@type error_info :: :erl_parse.error_info()
#@dialyzer [{:nowarn_function, :expr_to_handle, 3}]
@type expr :: :erl_parse.abstract_expr()
@type qlc_opt :: [any()] | tuple()
@type query_handle() :: :qlc.query_handle()
@type qlc_lc :: any
require Record
@qlc_handle_fields Record.extract(:qlc_handle, from_lib: "stdlib/src/qlc.erl")
@qlc_opt_fields Record.extract(:qlc_opt, from_lib: "stdlib/src/qlc.erl")
@qlc_lc_fields Record.extract(:qlc_lc, from_lib: "stdlib/src/qlc.erl")
Record.defrecord :qlc_handle, @qlc_handle_fields
Record.defrecord :qlc_opt, @qlc_opt_fields
Record.defrecord :qlc_lc, @qlc_lc_fields
@optkeys [:max_lookup,:cache, :join,:lookup,:unique]
@doc """
string to erlang ast
"""
@spec exprs(String.t) :: expr() | no_return()
def exprs(str) do
{:ok, m, _} =
str
|> String.to_charlist
|> :erl_scan.string
{: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, keys, acc) when is_list(keys) do
Enum.reduce(keys, acc, fn(key, acc) ->
default = if Enum.member?(opt, key) do
case key do
:unique -> true
:cache -> true
end
else
nil
end
case Keyword.get(opt, key, default) do
nil -> acc
val ->
# This is gross, but it's because we're using
# records. Otherwise you could just do this:
#
# qlc_opt(acc, Keyword.new([ { k, val }]))
#
case key do
:cache -> qlc_opt(acc, cache: val)
:max_lookup -> qlc_opt(acc, max_lookup: val)
:lookup -> qlc_opt(acc, lookup: val)
:join -> qlc_opt(acc, join: val)
:unique -> qlc_opt(acc, unique: val)
end
end
end)
end
@doc """
erlang ast with binding variables to qlc_handle
"""
@spec expr_to_handle(expr(), binding_struct, qlc_opt) :: query_handle() | {:qlc_handle, tuple()}
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)
opt_r = options(opt, @optkeys, qlc_opt())
lc = qlc_lc(q, opt: opt_r)
ret = qlc_handle(h: lc)
ret
end
@doc """
variable binding list to erlang_binding list
"""
@spec bind(Keyword.t, binding_struct) :: binding_struct
def bind([], b), do: b
def bind([{k, v} | t], b) when is_atom(k) do
bind(t, :erl_eval.add_binding(k, v, b))
end
@spec bind(Keyword.t) :: binding_struct
#def bind(a) when Keyword.keyword?is_list(a),
def bind(a) when is_list(a),
do: bind(a, :erl_eval.new_bindings())
@doc """
string to qlc_handle with variable bindings
"""
@spec string_to_handle(String.t, binding_struct, list) :: query_handle() | {:error,:qlc,{non_neg_integer() | {non_neg_integer(),pos_integer()},atom(),any()}}
def string_to_handle(str, bindings, opt \\ []) when is_binary(str) do
(String.ends_with?(str, ".") && str || str <> ".")
|> String.to_charlist
|> :qlc.string_to_handle(opt, bindings)
end
@doc """
string to qlc_handle with variable bindings.
string may be literal or variable.
If string is variable or function call, then
expanding to string_to_handle/3 automatically.
qlc expression string
## syntax
[Expression || Qualifier1, Qualifier2, ...]
Expression :: arbitary Erlang term (the template)
Qualifier :: Filter or Generators
Fiilter :: 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
## example
iex> require Qlc
iex> list = [a: 1,b: 2,c: 3]
iex> qlc_handle = Qlc.q("[X || X = {K,V} <- L, K =/= Item]",
...> [L: list, Item: :b])
...> Qlc.e(qlc_handle)
[a: 1, c: 3]
...> Qlc.q("[X || X = {K, V} <- L, K =:= Item]",
...> [L: qlc_handle, Item: :c]) |>
...> Qlc.e
[c: 3]
...> query_string = "[X || X = {K, V} <- L, K =:= Item]"
...> bindings = [L: list, Item: :b]
...> Qlc.q(query_string, bindings) |> Qlc.e()
[b: 2]
"""
#@spec q(String.t, bindings, list) :: query_handle
defmacro q(string, bindings, opt \\ []) do
case is_binary(string) do
true ->
exprl = (String.ends_with?(string, ".") && string || string <> ".")
|> exprs()
|> Macro.escape()
quote bind_quoted: [exprl: exprl, bindings: bindings, opt: opt] do
Qlc.expr_to_handle(exprl, bindings, opt)
end
false ->
quote bind_quoted: [string: string, bindings: bindings, opt: opt] do
Qlc.string_to_handle(string, bindings, opt)
end
end
end
@doc """
eval qlc_handle
"""
@spec e(query_handle) :: list
defdelegate e(qh), to: :qlc
@doc """
fold qlc_handle with accumulator
## example
iex> require Qlc
iex> list = [a: 1,b: 2,c: 3]
iex> qlc_handle = Qlc.q("[X || X = {K,V} <- L, K =/= Item]",
...> [L: list, Item: :b])
...> Qlc.fold(qlc_handle, [], fn({k,v}, acc) ->
...> [{v, k}|acc]
...> end)
[{3, :c}, {1, :a}]
"""
@spec fold(query_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(query_handle) :: query_cursor
def cursor(qh), do: %Qlc.Cursor{ c: :qlc.cursor(qh) }
@doc """
delete qlc cursor
(kill processes)
"""
@spec delete_cursor(Qlc.Cursor) :: :ok
def delete_cursor(qc), do: :qlc.delete_cursor(qc.c)
end