Skip to content

Commit 91dc464

Browse files
committed
improve compile time
closes #23
1 parent e90ebdd commit 91dc464

File tree

14 files changed

+532
-1951
lines changed

14 files changed

+532
-1951
lines changed

bench.exs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ sql = ~SQL[with recursive temp (n, fact) as (select 0, 1 union all select n+1, (
1111
query = "temp" |> recursive_ctes(true) |> with_cte("temp", as: ^union_all(select("temp", [t], %{n: 0, fact: 1}), ^where(select("temp", [t], [t.n+1, t.n+1*t.fact]), [t], t.n < 9))) |> select([t], [t.n])
1212
result = Tuple.to_list(SQL.Lexer.lex("with recursive temp (n, fact) as (select 0, 1 union all select n+1, (n+1)*fact from temp where n < 9)"))
1313
tokens = Enum.at(result, -1)
14-
context = Map.put(Enum.at(result, 1), :sql_lock, nil)
14+
context = Map.merge(Enum.at(result, 1), %{sql_lock: nil, module: SQL.Adapters.ANSI})
15+
{:ok, pcontext, ptokens} = SQL.Parser.parse(tokens, context)
1516
Benchee.run(
1617
%{
1718
"comptime to_string" => fn _ -> to_string(sql) end,
@@ -20,12 +21,18 @@ Benchee.run(
2021
"comptime ecto" => fn _ -> SQL.Repo.to_sql(:all, query) end,
2122
"lex" => fn _ -> SQL.Lexer.lex("with recursive temp (n, fact) as (select 0, 1 union all select n+1, (n+1)*fact from temp where n < 9)") end,
2223
"parse" => fn _ -> SQL.Parser.parse(tokens, context) end,
24+
"iodata" => fn _ -> pcontext.module.to_iodata(ptokens, pcontext, 0, []) end,
25+
"token_to_string" => fn _ -> pcontext.module.token_to_string(ptokens) end,
26+
"runtime dynamic" => fn _ -> ~SQL[from users] |> ~SQL[select *] |> ~SQL[where id = {{1+2*3}}] end,
2327
"runtime to_string" => fn _ -> to_string(~SQL[with recursive temp (n, fact) as (select 0, 1 union all select n+1, (n+1)*fact from temp where n < 9)]) end,
2428
"runtime to_sql" => fn _ -> SQL.to_sql(~SQL[with recursive temp (n, fact) as (select 0, 1 union all select n+1, (n+1)*fact from temp where n < 9)]) end,
2529
"runtime inspect" => fn _ -> inspect(~SQL[with recursive temp (n, fact) as (select 0, 1 union all select n+1, (n+1)*fact from temp where n < 9)]) end,
2630
"runtime ecto" => fn _ -> SQL.Repo.to_sql(:all, "temp" |> recursive_ctes(true) |> with_cte("temp", as: ^union_all(select("temp", [t], %{n: 0, fact: 1}), ^where(select("temp", [t], [t.n+1, t.n+1*t.fact]), [t], t.n < 9))) |> select([t], [t.n])) end
2731
},
2832
inputs: %{"1..100_000" => Enum.to_list(1..100_000)},
2933
memory_time: 2,
30-
reduction_time: 2
34+
reduction_time: 2,
35+
unit_scaling: :smallest,
36+
measure_function_call_overhead: true,
37+
profile_after: :eprof
3138
)

lib/adapters/ansi.ex

Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,31 @@ defmodule SQL.Adapters.ANSI do
2020
def token_to_string({tag, _, [left]}, mod) when tag in ~w[asc desc isnull notnull]a do
2121
"#{mod.token_to_string(left)} #{mod.token_to_string(tag)}"
2222
end
23-
def token_to_string({:fun, _, [left, right]}, mod) do
23+
def token_to_string({:not=tag, _, [{t,_,_} =left]}, mod) when t == :ident do
24+
"#{mod.token_to_string(left)} #{mod.token_to_string(tag)}"
25+
end
26+
def token_to_string({:fn, _, [left, right]}, mod) do
2427
"#{mod.token_to_string(left)}#{mod.token_to_string(right)}"
2528
end
29+
def token_to_string({:dot, _, [left, right]}, mod) do
30+
"#{mod.token_to_string(left)}.#{mod.token_to_string(right)}"
31+
end
32+
def token_to_string({tag, [{:type, :operator}|_], [{:not=t, _, left}, right]}, mod) do
33+
"#{mod.token_to_string(left)} #{mod.token_to_string(t)} #{mod.token_to_string(tag)} #{mod.token_to_string(right)}"
34+
end
2635
def token_to_string({tag, [{:type, :operator}|_], [left, right]}, mod) do
2736
"#{mod.token_to_string(left)} #{mod.token_to_string(tag)} #{mod.token_to_string(right)}"
2837
end
29-
def token_to_string({:ident, [{:type, :non_reserved},{:tag, tag}|_], [{:paren, _, _} = value]}, mod) do
38+
def token_to_string({:ident, [{:tag, tag}|_], [{:paren, _, _} = value]}, mod) do
3039
"#{mod.token_to_string(tag)}#{mod.token_to_string(value)}"
3140
end
32-
def token_to_string({:ident, [{:type, :non_reserved}, {:tag, tag}|_], [{:numeric, _, _} = value]}, mod) do
41+
def token_to_string({:ident, [{:tag, tag}|_], [{:numeric, _, _} = value]}, mod) do
3342
"#{mod.token_to_string(tag)} #{mod.token_to_string(value)}"
3443
end
35-
def token_to_string({:ident, [{:type, :non_reserved}, {:tag, tag}|_], _}, mod) do
44+
def token_to_string({:ident, [{:tag, tag}|_], _}, mod) do
3645
mod.token_to_string(tag)
3746
end
38-
def token_to_string({tag, [{:type, :reserved}|_], [{:paren, _, _} = value]}, mod) when tag not in ~w[on as in select]a do
47+
def token_to_string({tag, [{:type, :reserved}|_], [{:paren, _, _} = value]}, mod) when tag not in ~w[on in select]a do
3948
"#{mod.token_to_string(tag)}#{mod.token_to_string(value)}"
4049
end
4150
def token_to_string({tag, [{:type, :reserved}|_], []}, mod) do
@@ -59,12 +68,24 @@ defmodule SQL.Adapters.ANSI do
5968
def token_to_string({:comments, _, value}, _mod) do
6069
"\\*#{value}*\\"
6170
end
71+
def token_to_string({:double_quote, [{:prefix, prefix}|_], value}, _mod) do
72+
"#{prefix}\"#{value}\""
73+
end
6274
def token_to_string({:double_quote, _, value}, _mod) do
6375
"\"#{value}\""
6476
end
77+
def token_to_string({:quote, [{:prefix, prefix}|_], value}, _mod) do
78+
"#{prefix}'#{value}'"
79+
end
6580
def token_to_string({:quote, _, value}, _mod) do
6681
"'#{value}'"
6782
end
83+
def token_to_string({:backtick, [{:prefix, prefix}|_], value}, _mod) do
84+
"#{prefix}`#{value}`"
85+
end
86+
def token_to_string({:backtick, _, value}, _mod) do
87+
"`#{value}`"
88+
end
6889
def token_to_string({:paren, _, value}, mod) do
6990
"(#{mod.token_to_string(value)})"
7091
end
@@ -77,9 +98,6 @@ defmodule SQL.Adapters.ANSI do
7798
def token_to_string({:comma, _, value}, mod) do
7899
"#{mod.token_to_string(value)},"
79100
end
80-
def token_to_string({:dot, _, [left, right]}, mod) do
81-
"#{mod.token_to_string(left)}.#{mod.token_to_string(right)}"
82-
end
83101
def token_to_string({tag, _, value}, _mod) when tag in ~w[ident numeric]a do
84102
"#{value}"
85103
end
@@ -92,17 +110,17 @@ defmodule SQL.Adapters.ANSI do
92110
def token_to_string(value, _mod) when is_integer(value) do
93111
[value]
94112
end
95-
def token_to_string({tag, _, [left, right]}, mod) when tag in ~w[like ilike as union except intersect between and or is not in cursor for to]a do
113+
def token_to_string({tag, _, [left, right]}, mod) when tag in ~w[like ilike union except intersect between and or is not in cursor for to]a do
96114
"#{mod.token_to_string(left)} #{mod.token_to_string(tag)} #{mod.token_to_string(right)}"
97115
end
98-
def token_to_string({tag, [{:type, :reserved}|_], values}, mod) do
99-
"#{mod.token_to_string(tag)} #{mod.token_to_string(values)}"
116+
def token_to_string({tag, _, []}, mod) do
117+
mod.token_to_string(tag)
100118
end
101-
def token_to_string({tag, [{:type, :non_reserved}|_], values}, mod) when tag != :ident do
119+
def token_to_string({tag, [{:type, :reserved}|_], values=[_|_]}, mod) do
102120
"#{mod.token_to_string(tag)} #{mod.token_to_string(values)}"
103121
end
104-
def token_to_string({tag, _, []}, mod) do
105-
mod.token_to_string(tag)
122+
def token_to_string({tag, [{:type, :non_reserved}|_], values=[_|_]}, mod) when tag != :ident do
123+
"#{mod.token_to_string(tag)} #{mod.token_to_string(values)}"
106124
end
107125
def token_to_string(values, mod) do
108126
values
@@ -136,22 +154,31 @@ defmodule SQL.Adapters.ANSI do
136154
def to_iodata({tag, _, [left]}, context, indent) when tag in ~w[asc desc isnull notnull]a do
137155
[context.module.to_iodata(left, context, indent),?\s|context.module.to_iodata(tag, context, indent)]
138156
end
139-
def to_iodata({:fun, _, [left, right]}, context, indent) do
157+
def to_iodata({:fn, _, [left, right]}, context, indent) do
140158
[context.module.to_iodata(left, context, indent)|context.module.to_iodata(right, context, indent)]
141159
end
160+
def to_iodata({:dot, _, [left, right]}, context, indent) do
161+
[context.module.to_iodata(left, context, indent),?\.|context.module.to_iodata(right, context, indent)]
162+
end
163+
def to_iodata({tag, [{:type, :operator}|_], [{:not=t, _, left}, right]}, context, indent) do
164+
[context.module.to_iodata(left, context, indent),?\s,context.module.to_iodata(t, context, indent),?\s,context.module.to_iodata(tag, context, indent),?\s|context.module.to_iodata(right, context, indent)]
165+
end
142166
def to_iodata({tag, [{:type, :operator}|_], [left, {:paren, _, _} = right]}, context, indent) do
143167
[context.module.to_iodata(left, context, indent),?\s,context.module.to_iodata(tag, context, indent),?\s|context.module.to_iodata(right, context, indent)]
144168
end
145169
def to_iodata({tag, [{:type, :operator}|_], [left, right]}, context, indent) do
146170
[context.module.to_iodata(left, context, indent),?\s,context.module.to_iodata(tag, context, indent),?\s|context.module.to_iodata(right, context, indent)]
147171
end
148-
def to_iodata({:ident, [{:type, :non_reserved},{:tag, tag}|_], [{:paren, _, _} = value]}, context, indent) do
172+
def to_iodata({tag, [{:type, :operator}|_], []}, context, indent) do
173+
[context.module.to_iodata(tag, context, indent)]
174+
end
175+
def to_iodata({:ident, [{:tag, tag}|_], [{:paren, _, _} = value]}, context, indent) do
149176
[context.module.to_iodata(tag, context, indent)|context.module.to_iodata(value, context, indent)]
150177
end
151-
def to_iodata({:ident, [{:type, :non_reserved}, {:tag, tag}|_], [{:numeric, _, _} = value]}, context, indent) do
178+
def to_iodata({:ident, [{:tag, tag}|_], [{:numeric, _, _} = value]}, context, indent) do
152179
[context.module.to_iodata(tag, context, indent),?\s|context.module.to_iodata(value, context, indent)]
153180
end
154-
def to_iodata({:ident, [{:type, :non_reserved}, {:tag, tag}|_], _}, context, indent) do
181+
def to_iodata({:ident, [{:tag, tag}|_], _}, context, indent) do
155182
context.module.to_iodata(tag, context, indent)
156183
end
157184
def to_iodata({tag, [{:type, :reserved}|_], [{:paren, _, _} = value]}, context, indent) when tag not in ~w[on in select]a do
@@ -178,15 +205,30 @@ defmodule SQL.Adapters.ANSI do
178205
def to_iodata({:comments, _, value}, _context, _indent) do
179206
[?\\,?*,value|[?*, ?\\]]
180207
end
208+
def to_iodata({:double_quote, [{:prefix, prefix}|_], value}=node, context, _indent) do
209+
case node in context.errors do
210+
true -> [[prefix,?",:red,value|[:reset, ?"]]]
211+
false -> [prefix, ?", value, ?"]
212+
end
213+
end
181214
def to_iodata({:double_quote, _, value}=node, context, _indent) do
182215
case node in context.errors do
183216
true -> [[?",:red,value|[:reset, ?"]]]
184-
false -> value
217+
false -> [?", value, ?"]
185218
end
186219
end
220+
def to_iodata({:quote, [{:prefix, prefix}|_], value}, _context, _indent) do
221+
[prefix,?',value|[?']]
222+
end
187223
def to_iodata({:quote, _, value}, _context, _indent) do
188224
[?',value|[?']]
189225
end
226+
def to_iodata({:backtick, [{:prefix, prefix}|_], value}, _context, _indent) do
227+
[prefix,?`,value|[?`]]
228+
end
229+
def to_iodata({:backtick, _, value}, _context, _indent) do
230+
[?`,value|[?`]]
231+
end
190232
def to_iodata({:paren, _, [{_,[{:type, :reserved}|_],_}|_] = value}, context, indent) do
191233
[?(,?\n, context.module.to_iodata(value, context, indent+1)|?)]
192234
end
@@ -202,9 +244,6 @@ defmodule SQL.Adapters.ANSI do
202244
def to_iodata({:comma, _, value}, context, indent) do
203245
[context.module.to_iodata(value, context, indent), ?,, ?\s]
204246
end
205-
def to_iodata({:dot, _, [left, right]}, context, indent) do
206-
[context.module.to_iodata(left, context, indent),?\.|context.module.to_iodata(right, context, indent)]
207-
end
208247
def to_iodata({tag, _, value} = node, context, _indent) when tag in ~w[ident numeric]a do
209248
case node in context.errors do
210249
true -> [:red, value, :reset]
@@ -226,10 +265,10 @@ defmodule SQL.Adapters.ANSI do
226265
def to_iodata({tag, _, [left, right]}, context, indent) when tag in ~w[like ilike union except intersect between and or is not in cursor for to]a do
227266
[context.module.to_iodata(left, context, indent),?\s,context.module.to_iodata(tag, context, indent),?\s|context.module.to_iodata(right, context, indent)]
228267
end
229-
def to_iodata({tag, [{:type, :reserved}|_], values}, context, indent) do
268+
def to_iodata({tag, [{:type, :reserved}|_], values=[_|_]}, context, indent) do
230269
[context.module.to_iodata(tag, context, indent),?\s|context.module.to_iodata(values, context, indent)]
231270
end
232-
def to_iodata({tag, [{:type, :non_reserved}|_], values}, context, indent) when tag != :ident do
271+
def to_iodata({tag, [{:type, :non_reserved}|_], values=[_|_]}, context, indent) when tag != :ident do
233272
[context.module.to_iodata(tag, context, indent),?\s|context.module.to_iodata(values, context, indent)]
234273
end
235274
def to_iodata({tag, _, []}, context, indent) do

lib/adapters/postgres.ex

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ defmodule SQL.Adapters.Postgres do
1111

1212
@doc false
1313
def token_to_string(value, mod \\ __MODULE__)
14-
def token_to_string({:not, _, [left, {:in, _, [{:binding, _, _} = right]}]}, mod), do: "#{mod.token_to_string(left)} != ANY(#{mod.token_to_string(right)})"
14+
def token_to_string({:in, _, [{:not, _, left}, {:binding, _, _} = right]}, mod), do: "#{mod.token_to_string(left)} != ANY(#{mod.token_to_string(right)})"
1515
def token_to_string({:in, _, [left, {:binding, _, _} = right]}, mod), do: "#{mod.token_to_string(left)} = ANY(#{mod.token_to_string(right)})"
1616
def token_to_string({:binding, _, [idx]}, _mod) when is_integer(idx), do: "$#{idx}"
1717
def token_to_string({tag, _, [left, right]}, mod) when tag in ~w[>>=]a do
@@ -20,10 +20,10 @@ defmodule SQL.Adapters.Postgres do
2020
def token_to_string(token, mod), do: SQL.Adapters.ANSI.token_to_string(token, mod)
2121

2222
@doc false
23-
def to_iodata({:not, _, [left, {:in, _, [{:binding, _, [idx]}]}]}, %{format: true, binding: binding} = context, indent) do
24-
[context.module.to_iodata(left, context, indent), ?\s, ?{,?{,Macro.to_string(Enum.at(binding, idx-1)),?},?}]
23+
def to_iodata({:in, _, [{:not=t, _, left}, {:binding, _, [idx]}]}, %{format: true, binding: binding} = context, indent) do
24+
[context.module.to_iodata(left, context, indent),?\s,context.module.to_iodata(t, context, indent),?\s, ?{,?{,Macro.to_string(Enum.at(binding, idx-1)),?},?}]
2525
end
26-
def to_iodata({:not, _, [left, {:in, _, [{:binding, _, _} = right]}]}, context, indent) do
26+
def to_iodata({:in, _, [{:not, _, left}, {:binding, _, _}=right]}, context, indent) do
2727
[context.module.to_iodata(left, context, indent), ?!, ?=, ?A,?N,?Y,?(, context.module.to_iodata(right, context, indent), ?)]
2828
end
2929
def to_iodata({:in, _, [left, {:binding, _, [idx]}]}, %{format: true, binding: binding} = context, indent) do

lib/bnf.ex

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ defmodule SQL.BNF do
4343
cond do
4444
String.ends_with?(r, "word>") == true ->
4545
e = if is_map_key(opts, r), do: e ++ opts[r], else: e
46-
{[{r, (for v <- e, v not in ["|", "AS"], do: {atom(v), match(v), guard(v)})} | keywords], operators, letters, digits, terminals}
46+
{[{r, (for v <- e, v not in ["|", "AS"], do: {atom(v), qouted_match(v), qouted_guard(v)})} | keywords], operators, letters, digits, terminals}
4747
String.ends_with?(r, "letter>") == true -> {keywords, operators, [{r, Enum.reject(e, &(&1 == "|"))}|letters], digits, terminals}
4848
String.ends_with?(r, "digit>") == true -> {keywords, operators, letters, [{r, Enum.reject(e, &(&1 == "|"))}|digits], terminals}
4949
String.ends_with?(r, "operator>") == true -> {keywords, [rule | operators], letters, digits, terminals}
@@ -61,8 +61,8 @@ defmodule SQL.BNF do
6161
{_, _, [b |_]} -> byte_size(b)
6262
end, :desc)
6363
|> Enum.map(fn
64-
{r, e} -> {r, (for v <- e, do: {String.to_atom(v), inline_match(v)})}
65-
{r, _, e} -> {r, (for v <- e, do: {String.to_atom(v), inline_match(v)})}
64+
{r, e} -> {r, (for v <- e, do: {String.to_atom(v), qouted_inline_match(v)})}
65+
{r, _, e} -> {r, (for v <- e, do: {String.to_atom(v), qouted_inline_match(v)})}
6666
end)
6767
special_characters = Enum.filter(non_terminals ++ root, &(String.ends_with?(elem(&1, 0), "special character>") || String.ends_with?(elem(&1, 0), "special symbol>")))
6868
special_characters = for {_, e} <- special_characters, v <- e, v != "|", do: {String.to_atom(v), elem(Enum.find(symbols, {v, v}, &elem(&1, 0) == v), 1)}
@@ -196,23 +196,48 @@ defmodule SQL.BNF do
196196

197197
def cast(<<?<, b, _::binary>> = expr) when b in ?a..?z or b in ?A..?Z, do: expr
198198
def cast(expr) when expr in ["|", "{", "}", "[", "]", :ignore, :self, "\u0020", "\u0009", "\u000D", "\u00A0", "\u00A0", "\u1680", "\u2000", "\u2001", "\u2002", "\u2003", "\u2004", "\u2005", "\u2006", "\u2007", "\u2008", "\u2009", "\u200A", "\u202F", "\u205F", "\u3000", "\u180E", "\u200B", "\u200C", "\u200D", "\u2060", "\uFEFF", "\u000A", "\u000B", "\u000C", "\u000D", "\u0085", "\u2028", "\u2029"], do: expr
199-
def cast(expr) when is_binary(expr), do: {atom(expr), match(expr), guard(expr)}
199+
def cast(expr) when is_binary(expr), do: {atom(expr), qouted_match(expr), qouted_guard(expr)}
200200
def cast(expr) when is_tuple(expr) or is_list(expr) or is_atom(expr), do: expr
201201

202202
def atom(value), do: String.to_atom(String.replace(String.replace(String.downcase(value), ["<", ">"], ""), ["/", " "], "_"))
203-
def match(value), do: Enum.reduce(1..byte_size(value), "[]", fn n, acc -> "[#{acc}, b#{n}]" end)
204-
def inline_match(value) do
205-
for <<k <- value>>, reduce: "[]" do
206-
acc -> "[#{acc}, ?#{<<k>>}]"
203+
# def match(value), do: Enum.reduce(1..byte_size(value), "[]", fn n, acc -> "[#{acc}, b#{n}]" end)
204+
# def inline_match(value) do
205+
# for <<k <- value>>, reduce: "[]" do
206+
# acc -> "[#{acc}, ?#{<<k>>}]"
207+
# end
208+
# end
209+
210+
# def guard(value) do
211+
# {value, _n} = for <<k <- String.downcase(value)>>, reduce: {"", 1} do
212+
# {"", n} -> {guard(k, n), n+1}
213+
# {acc, n} -> {"#{acc} and #{guard(k, n)}", n+1}
214+
# end
215+
# value
216+
# end
217+
# def guard(k, n), do: "b#{n} in #{inspect(Enum.uniq(~c"#{<<k>>}#{String.upcase(<<k>>)}"))}"
218+
219+
@doc false
220+
def qouted_match(value), do: Enum.reduce(1..byte_size(value), [], fn n, acc -> [acc | [{:"b#{n}", [], Elixir}]] end)
221+
222+
@doc false
223+
def qouted_inline_match(value) do
224+
for <<k <- value>>, reduce: [] do
225+
acc -> [acc, k]
207226
end
208227
end
209-
def guard(value) do
210-
{value, _n} = for <<k <- String.downcase(value)>>, reduce: {"", 1} do
211-
{"", n} -> {guard(k, n), n+1}
212-
{acc, n} -> {"#{acc} and #{guard(k, n)}", n+1}
228+
229+
@doc false
230+
def qouted_guard(value, acc \\ []) do
231+
{value, _n} = for <<k <- String.downcase(value)>>, reduce: {acc, 1} do
232+
{[], n} -> {__guard__(k, n), n+1}
233+
{acc, n} -> {{:and, [context: Elixir, imports: [{2, Kernel}]], [acc,__guard__(k, n)]}, n+1}
213234
end
214235
value
215236
end
216-
def guard(k, n) when k in ?a..?z, do: "is_#{<<k>>}(b#{n})"
217-
def guard(k, n), do: "b#{n} in #{inspect(Enum.uniq(~c"#{<<k>>}#{String.upcase(<<k>>)}"))}"
237+
238+
@doc false
239+
def __guard__(k, n) do
240+
{:in, [context: Elixir, imports: [{2, Kernel}]],[{:"b#{n}", [], Elixir},{:sigil_c, [delimiter: "\"", context: Elixir, imports: [{2, Kernel}]],[{:<<>>, [], ["#{<<k>>}#{String.upcase(<<k>>)}"]}, []]}]}
241+
end
242+
218243
end

0 commit comments

Comments
 (0)