Packages
SQL provides state-of-the-art, high-performance SQL integration for Elixir, built to handle extreme concurrency with unmatched expressiveness and ergonomic query composition. Write safe, composable, parameterized queries directly, without translating to Ecto or any ORM.
Current section
Files
Jump to
Current section
Files
lib/parser.ex
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2025 DBVisor
defmodule SQL.Parser do
@moduledoc false
@compile {:inline, validate: 3, validate_columns: 3, validate_table: 3, sort: 1}
def describe(tokens, columns), do: description(tokens, columns, [], [], [])
def parse(tokens, context), do: parse(tokens, context, [], [], [], [], [])
defp parse([], context, [], [], [], tokens, errors) do
{:ok, %{context | errors: errors++context.errors}, tokens}
end
defp parse([], context, [], [], acc, tokens, errors) do
sorted = sort(acc)
context = if sorted != acc do
%{context|format: :dynamic}
else
context
end
{:ok, %{context | errors: errors++context.errors}, sorted++tokens}
end
defp parse([], context, a, acc, [], [], errors) do
{:ok, %{context | errors: errors++context.errors}, __parse__(a)++acc}
end
defp parse([], context, a, [], root, [], errors) do
{:ok, %{context | errors: errors++context.errors}, __parse__(a)++root}
end
defp parse([], context, unit, acc, root, [], errors) do
{:ok, %{context | errors: errors++context.errors}, unit++acc++root}
end
defp parse([{:paren=t,[{_, {l, c, l, _, 0, 0, 0, 0}}|_]=mm,a}, {:ident,[{_, {l, _, l, c, _, _}}|_]=m,v}|tokens], context, unit, acc, root, acc2, errors) do
{:ok, context, a} = parse(a, context)
parse(tokens, context, [{:"#{:string.lowercase(v)}",m,[{t,mm,a}]}|unit], acc, root, acc2, errors)
end
defp parse([{:paren=t,m,a},{tt,[_,{:type, :reserved}|_]=mm,aa}|tokens], context, unit, acc, root, acc2, errors) when tt not in ~w[select where group having order limit offset join over]a do
{:ok, context, a} = parse(a, context)
parse(tokens, context, [{tt,mm,[{t,m,a}|aa]}|unit], acc, root, acc2, errors)
end
defp parse([{:with=t, m, unit}|tokens], context, a, acc, root, acc2, errors) do
sorted = sort(root)
context = if sorted != root do
%{context|format: :dynamic}
else
context
end
parse(tokens, context, unit, unit, [{t, m, __parse__(a)++acc++sorted}], acc2, errors)
end
defp parse([{:colon=t, m, a}, {tag, _, _} = node|tokens], context, unit, acc, root, acc2, errors) when tag in ~w[begin commit rollback]a do
parse(tokens, context, unit, acc, [{t, m, [node|a]}|root], acc2, errors)
end
defp parse([{:comma=t, m, a}|tokens], context, unit, acc, root, acc2, errors) do
parse(tokens, context, a, [{t, m, __parse__(unit)}|acc], root, acc2, errors)
end
defp parse([{:when=t, m, a}|tokens], context, unit, acc, root, acc2, errors) do
Process.put(:status, true)
parse(tokens, context, a, [{t, m, __parse__(unit)}|acc], root, acc2, errors)
end
defp parse([{:else=t, m, a}|tokens], context, unit, acc, root, acc2, errors) do
parse(tokens, context, a, [{t, m, __parse__(unit)}|acc], root, acc2, errors)
end
defp parse([{:colon=t, tm, ta}|tokens], context, []=unit, []=acc, root, acc2, errors) do
{:ok, context, ta} = parse(ta, context)
sorted = sort(root)
context = if sorted != root do
%{context|format: :dynamic}
else
context
end
parse(tokens, context, unit, acc, acc, [{t, tm, ta},sorted|acc2], errors)
end
defp parse([{:quote,_,_}=r, {:interval=t,m,a}|tokens], context, unit, acc, root, acc2, errors) do
parse(tokens, context, [{t,m,[r|a]}|unit], acc, root, acc2, errors)
end
defp parse([{:dot=t,tm,ta},l|tokens], context, [{:dot,_,_}=r|unit], acc, root, acc2, errors) do
parse(tokens, context, [{t,tm,[l,r|ta]}|unit], acc, root, acc2, errors)
end
defp parse([l,{:dot=t,tm,ta},r|tokens], context, unit, acc, root, acc2, errors) do
parse(tokens, context, [{t,tm,[r,l|ta]}|unit], acc, root, acc2, errors)
end
defp parse([r,{:in=t,tm,ta},l,{t2,[_,_,{:tag,t3}|_]=t3m,_},{:fetch=f,fm,fa}|tokens], context, []=unit, []=acc, root, acc2, errors) when t3 in ~w[absolute relative]a or t2 in ~w[backward forward]a do
parse(tokens, context, unit, acc, [{f,fm,[{t,tm,[{t3,t3m,[l]},r|ta]}|fa]}|root], acc2, errors)
end
defp parse([r,{:in=t,tm,ta},{_,[_,_,{:tag,l}|_]=lm,_la},{:fetch=f,fm,fa}|tokens], context, []=unit, []=acc, root, acc2, errors) do
parse(tokens, context, unit, acc, [{f,fm,[{t,tm,[{l,lm,[]},r|ta]}|fa]}|root], acc2, errors)
end
defp parse([r,{:in=t,tm,ta},l,{:fetch=f,fm,fa}|tokens], context, []=unit, []=acc, root, acc2, errors) do
parse(tokens, context, unit, acc, [{f,fm,[{t,tm,[l,r|ta]}|fa]}|root], acc2, errors)
end
defp parse([r,{:from=t,tm,ta},l,{t2,[_,_,{:tag,t3}|_]=t3m,_},{:fetch=f,fm,fa}|tokens], context, []=unit, []=acc, root, acc2, errors) when t3 in ~w[absolute relative]a or t2 in ~w[backward forward]a do
parse(tokens, context, unit, acc, [{f,fm,[{t3,t3m,[l]}, {t,tm,[r|ta]}|fa]}|root], acc2, errors)
end
defp parse([r,{:from=t,tm,ta},{_,[_,_,{:tag,l}|_]=lm,_la},{:fetch=f,fm,fa}|tokens], context, []=unit, []=acc, root, acc2, errors) do
parse(tokens, context, unit, acc, [{f,fm,[{l,lm,[]},{t,tm,[r|ta]}|fa]}|root], acc2, errors)
end
defp parse([r,{:from=t,tm,ta},l,{:fetch=f,fm,fa}|tokens], context, []=unit, []=acc, root, acc2, errors) do
parse(tokens, context, unit, acc, [{f,fm,[l,{t,tm,[r|ta]}|fa]}|root], acc2, errors)
end
defp parse([{t,m,unit}|tokens], context, a, acc, root, acc2, errors) when t in ~w[by on into]a do
parse(tokens, context, unit, [{t,m,__parse__(a)++acc}], root, acc2, errors)
end
defp parse([{_,[_,_,{:tag,t}|_]=m,_}|tokens], context, unit, acc, root, acc2, errors) when t in ~w[asc desc]a do
parse(tokens, context, [{t,m,[]}|unit], acc, root, acc2, errors)
end
defp parse([{:paren=r,rm,ra},{:all=a, am, aa},{t, tm, []=ta}, {:paren=l, lm, la}|tokens], context, unit, acc, root, acc2, errors) when t in ~w[except intersect union]a do
{:ok, context, la} = parse(la, context)
{:ok, context, ra} = parse(ra, context)
parse(tokens, context, unit, acc, [{t,tm,[{l,lm,la},{a, am, [{r, rm, ra}|aa]}|ta]}|root], acc2, errors)
end
defp parse([{:paren=r,rm,ra},{t, tm, []=ta},{:paren=l, lm, la}|tokens], context, unit, acc, root, acc2, errors) when t in ~w[except intersect union]a do
{:ok, context, la} = parse(la, context)
{:ok, context, ra} = parse(ra, context)
parse(tokens, context, unit, acc, [{t,tm,[{l,lm,la},{r,rm,ra}|ta]}|root], acc2, errors)
end
defp parse([{a, am, []}, {t, tm, []=ta}, {:paren=l, lm, la}|tokens], context, unit, acc, root, acc2, errors) when t in ~w[except intersect union]a and a in ~w[all distinct]a do
{:ok, context, la} = parse(la, context)
sorted = sort(root)
context = if sorted != root do
%{context|format: :dynamic}
else
context
end
parse(tokens, context, unit, acc, [{t,tm,[{l, lm, la},{a, am, sorted}|ta]}], acc2, errors)
end
defp parse([{t, tm, []=ta}, {:paren=r,rm,ra}|tokens], context, []=unit, []=acc, root, acc2, errors) when t in ~w[except intersect union]a do
{:ok, context, ra} = parse(ra, context)
sorted = sort(root)
context = if sorted != root do
%{context|format: :dynamic}
else
context
end
parse(tokens, context, unit, acc, [{t,tm,[{r,rm,ra},sorted|ta]}], acc2, errors)
end
defp parse([{a, am, []}, {t, tm, []=ta}|tokens], context, unit, []=acc, root, acc2, errors) when t in ~w[except intersect union]a and a in ~w[all distinct]a do
right = if unit == [], do: root, else: unit
{:ok, context, la} = parse(tokens, context)
sorted = sort(right)
context = if sorted != right do
%{context|format: :dynamic}
else
context
end
parse([], context, acc, acc, [{t,tm,[la,{a,am, sorted}|ta]}], acc2, errors)
end
defp parse([{t, tm, []=ta}|tokens], context, []=unit, []=acc, root, acc2, errors) when t in ~w[except intersect union]a do
{:ok, context, la} = parse(tokens, context)
sorted = sort(root)
context = if sorted != root do
%{context|format: :dynamic}
else
context
end
parse([], context, unit, acc, [{t,tm,[la, sorted|ta]}], acc2, errors)
end
defp parse([{:join=t,m,[]=unit}, {:outer=o, om, oa}, {l, lm, la}, {:natural=n, nm, na}|tokens], context, [{tag,_,_}|_]=a, acc, root, acc2, errors) when l in ~w[left right full]a and tag in ~w[double_quote bracket dot ident as paren]a do
node = {t,m,__parse__(a)++acc}
parse(tokens, context, unit, unit, [{n, nm, [{l, lm, [{o, om, [node|oa]}|la]}|na]}|root], acc2, validate(node, context, errors))
end
defp parse([{:join=t,m,[]=unit}, {:outer=o, om, oa}, {l, lm, la}|tokens], context, [{tag,_,_}|_]=a, acc, root, acc2, errors) when l in ~w[left right full]a and tag in ~w[double_quote bracket dot ident as paren]a do
node = {t,m,__parse__(a)++acc}
parse(tokens, context, unit, unit, [{l, lm, [{o, om, [node|oa]}|la]}|root], acc2, validate(node, context, errors))
end
defp parse([{:join=t,m,[]=unit}, {:inner=i, im, ia}, {:natural=n, nm, []=na}|tokens], context, [{tag,_,_}|_]=a, acc, root, acc2, errors) when tag in ~w[double_quote bracket dot ident as paren]a do
node = {t,m,__parse__(a)++acc}
parse(tokens, context, unit, unit, [{n, nm, [{i, im, [node|ia]}|na]}|root], acc2, validate(node, context, errors))
end
defp parse([{:join=t,m,[]=unit}, {l, lm, []=la}|tokens], context, [{tag,_,_}|_]=a, acc, root, acc2, errors) when l in ~w[inner left right full natural cross]a and tag in ~w[double_quote bracket dot ident as paren]a do
node = {t,m,__parse__(a)++acc}
parse(tokens, context, unit, unit, [{l, lm, [node|la]}|root], acc2, validate(node, context, errors))
end
defp parse([{:join=t,m,[]=unit}|tokens], context, [{tag,_,_}|_]=a, acc, root, acc2, errors) when tag in ~w[double_quote bracket dot ident as paren]a do
node = {t,m,__parse__(a)++acc}
parse(tokens, context, unit, unit, [node|root], acc2, validate(node, context, errors))
end
defp parse([{:from=t,m,[]=unit}|tokens], context, [{tag,_,_}|_]=a, acc, root, acc2, errors) when tag in ~w[double_quote bracket dot ident as generate_series]a do
node = {t,m,__parse__(a)++acc}
parse(tokens, context, unit, unit, [node|root], acc2, validate(node, context, errors))
end
defp parse([{t,m,[]=unit}|tokens], context, a, acc, root, acc2, errors) when t in ~w[select where group having order limit offset delete update set insert]a do
node = {t,m,__parse__(a)++acc}
parse(tokens, context, unit, unit, [node|root], acc2, validate(node, context, errors))
end
defp parse([{_,[span, type, {:tag, t} | m],_}|tokens], context, a, acc, root, acc2, errors) when t in ~w[returning]a do
node = {t,[span, type|m],__parse__(a)++acc}
parse(tokens, context, [], [], [node|root], acc2, validate(node, context, errors))
end
defp parse([{t,m,a}|tokens], context, unit, acc, root, acc2, errors) when t in ~w[paren bracket]a do
{:ok, context, a} = parse(a, context)
parse(tokens, context, [{t,m,a}|unit], acc, root, acc2, errors)
end
defp parse([{:case=t,m,a}|tokens], context, unit, acc, root, acc2, errors) do
{:ok, context, a} = parse(a, context)
parse(tokens, context, [{t,m,a}|unit], acc, root, acc2, errors)
end
defp parse([{:table=tt,mt,at}, {:create=tc,mc,ac}|tokens], context, unit, acc, root, acc2, errors) do
parse(tokens, context, at, acc, [{tc,mc,[{tt,mt,unit++acc}|ac]}|root], acc2, errors)
end
defp parse([node|tokens], context, unit, acc, root, acc2, errors) do
parse(tokens, context, [node|unit], acc, root, acc2, errors)
end
@literal ~w[numeric ident double_quote quote bracket dot binding paren some ::]a
@comparison ~w[= != <> < > <= >= in is like ilike between notnull isnull and or]a
defp __parse__(tokens) do
case tokens do
[{tl, _, [_,_]}=l, {t=:then, m, []=a}, r|[]=rest] when tl in ~w[and or =]a -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, [_,_]}=l, {t=:and, m, []=a}, {tr, _, [_,_]}=r|rest] when tl in @comparison and tr in @comparison -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, [_,_]}=l, {t=:or, m, []=a}, {tr, _, [_,_]}=r|rest] when tl in @comparison and tr in @comparison -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, [_,_]}=l, {t=:and, m, []=a}, {tr, _, []}=r|rest] when tl in @comparison and tr in ~w[true false null unknown]a -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, [_,_]}=l, {t=:or, m, []=a}, {tr, _, []}=r|rest] when tl in @comparison and tr in ~w[true false null unknown]a -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, []}=l, {t=:and, m, []=a}, {tr, _, [_,_]}=r|rest] when tl in ~w[true false null unknown]a and tr in @comparison -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, []}=l, {t=:or, m, []=a}, {tr, _, [_,_]}=r|rest] when tl in ~w[true false null unknown]a and tr in @comparison -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, [_,_]}=l, {:and, _, []}=r|rest] when tl in @comparison ->
__parse__([l,r|__parse__(rest)])
[{tl, _, [_,_]}=l, {:or, _, []}=r|rest] when tl in @comparison ->
__parse__([l,r|__parse__(rest)])
[{tl, _, _}=l, {t=:asc, m, []=a}|rest] when tl in @literal -> __parse__([{t, m, [l|a]}|rest])
[{tl, _, _}=l, {t=:desc, m, []=a}|rest] when tl in @literal -> __parse__([{t, m, [l|a]}|rest])
[l, {t=:*, m, []=a}, r|[]=rest] -> __parse__([{t, m, [l,r|a]}|rest])
[l, {t=:/, m, []=a}, r|[]=rest] -> __parse__([{t, m, [l,r|a]}|rest])
[l, {t=:%, m, []=a}, r|[]=rest] -> __parse__([{t, m, [l,r|a]}|rest])
[l, {t=:+, m, []=a}, r|[]=rest] -> __parse__([{t, m, [l,r|a]}|rest])
[l, {t=:-, m, []=a}, r|[]=rest] -> __parse__([{t, m, [l,r|a]}|rest])
[l, {t=:*, m, []=a}, r|[{tr, _, _}|_]=rest] when tr in ~w[and or then]a -> __parse__([{t, m, [l,r|a]}|rest])
[l, {t=:/, m, []=a}, r|[{tr, _, _}|_]=rest] when tr in ~w[and or then]a -> __parse__([{t, m, [l,r|a]}|rest])
[l, {t=:%, m, []=a}, r|[{tr, _, _}|_]=rest] when tr in ~w[and or then]a -> __parse__([{t, m, [l,r|a]}|rest])
[l, {t=:+, m, []=a}, r|[{tr, _, _}|_]=rest] when tr in ~w[and or then]a -> __parse__([{t, m, [l,r|a]}|rest])
[l, {t=:-, m, []=a}, r|[{tr, _, _}|_]=rest] when tr in ~w[and or then]a -> __parse__([{t, m, [l,r|a]}|rest])
[l, {t=:=, m, []=a}, r|[{tr, _, _}|_]=rest] when tr in ~w[and or then]a -> __parse__([{t, m, [l,r|a]}|rest])
[l, {t=:!=, m, []=a}, r|[{tr, _, _}|_]=rest] when tr in ~w[and or then]a -> __parse__([{t, m, [l,r|a]}|rest])
[l, {t=:<>, m, []=a}, r|[{tr, _, _}|_]=rest] when tr in ~w[and or then]a -> __parse__([{t, m, [l,r|a]}|rest])
[l, {t=:<, m, []=a}, r|[{tr, _, _}|_]=rest] when tr in ~w[and or then]a -> __parse__([{t, m, [l,r|a]}|rest])
[l, {t=:>, m, []=a}, r|[{tr, _, _}|_]=rest] when tr in ~w[and or then]a -> __parse__([{t, m, [l,r|a]}|rest])
[l, {t=:<=, m, []=a}, r|[{tr, _, _}|_]=rest] when tr in ~w[and or then]a -> __parse__([{t, m, [l,r|a]}|rest])
[l, {t=:>=, m, []=a}, r|[{tr, _, _}|_]=rest] when tr in ~w[and or then]a -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, _}=l, {t=:=, m, []=a}, r|[]=rest] when tl in @literal -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, _}=l, {t=:!=, m, []=a}, r|[]=rest] when tl in @literal -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, _}=l, {t=:<>, m, []=a}, r|[]=rest] when tl in @literal -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, _}=l, {t=:<, m, []=a}, r|[]=rest] when tl in @literal -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, _}=l, {t=:>, m, []=a}, r|[]=rest] when tl in @literal -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, _}=l, {t=:<=, m, []=a}, r|[]=rest] when tl in @literal -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, _}=l, {t=:>=, m, []=a}, r|[]=rest] when tl in @literal -> __parse__([{t, m, [l,r|a]}|rest])
[l, {:=, _, _}=r|rest] ->
__parse__([l,r|__parse__(rest)])
[l, {:!=, _, _}=r|rest] ->
__parse__([l,r|__parse__(rest)])
[l, {:<>, _, _}=r|rest] ->
__parse__([l,r|__parse__(rest)])
[l, {:<, _, _}=r|rest] ->
__parse__([l,r|__parse__(rest)])
[l, {:>, _, _}=r|rest] ->
__parse__([l,r|__parse__(rest)])
[l, {:<=, _, _}=r|rest] ->
__parse__([l,r|__parse__(rest)])
[l, {:>=, _, _}=r|rest] ->
__parse__([l,r|__parse__(rest)])
[{_, _, _}=l, {t=:"::", m, []=a}, {_, _, _}=r, {bt=:bracket, bm, ba=[]}|rest] -> __parse__([{t, m, [l,{bt, bm, [r|ba]}|a]}|rest])
[{_, _, _}=l, {t=:"::", m, []=a}, {_, _, _}=r|rest] -> __parse__([{t, m, [l,r|a]}|rest])
[{_, _, _}=l, {t=:"||", m, []=a}, {_, _, _}=r|rest] -> __parse__([{t, m, [l,r|a]}|rest])
[{_, _, _}=l, {t=:"->", m, []=a}, {_, _, _}=r|rest] -> __parse__([{t, m, [l,r|a]}|rest])
[{_, _, _}=l, {t=:"->>", m, []=a}, {_, _, _}=r|rest] -> __parse__([{t, m, [l,r|a]}|rest])
[{_, _, _}=l, {t=:"-|-", m, []=a}, {_, _, _}=r|rest] -> __parse__([{t, m, [l,r|a]}|rest])
[{_, _, _}=l, {t=:">>=", m, []=a}, {_, _, _}=r|rest] -> __parse__([{t, m, [l,r|a]}|rest])
[{_, _, _}=l, {t=:"@@", m, []=a}, {_, _, _}=r|rest] -> __parse__([{t, m, [l,r|a]}|rest])
[{_, _, _}=l, {t=:"|&>", m, []=a}, {_, _, _}=r|rest] -> __parse__([{t, m, [l,r|a]}|rest])
[{_, _, _}=l, {t=:"|>>", m, []=a}, {_, _, _}=r|rest] -> __parse__([{t, m, [l,r|a]}|rest])
[{_, _, _}=l, {t=:"<<|", m, []=a}, {_, _, _}=r|rest] -> __parse__([{t, m, [l,r|a]}|rest])
[{_, _, _}=l, {t=:"&<|", m, []=a}, {_, _, _}=r|rest] -> __parse__([{t, m, [l,r|a]}|rest])
[{_, _, _}=l, {t=:"<@", m, []=a}, {_, _, _}=r|rest] -> __parse__([{t, m, [l,r|a]}|rest])
[{_, _, _}=l, {t=:"@>", m, []=a}, {_, _, _}=r|rest] -> __parse__([{t, m, [l,r|a]}|rest])
[{_, _, _}=l, {t=:"~=", m, []=a}, {_, _, _}=r|rest] -> __parse__([{t, m, [l,r|a]}|rest])
[{_, _, _}=l, {t=:">>", m, []=a}, {_, _, _}=r|rest] -> __parse__([{t, m, [l,r|a]}|rest])
[{_, _, _}=l, {t=:"&>", m, []=a}, {_, _, _}=r|rest] -> __parse__([{t, m, [l,r|a]}|rest])
[{_, _, _}=l, {t=:"&&", m, []=a}, {_, _, _}=r|rest] -> __parse__([{t, m, [l,r|a]}|rest])
[{_, _, _}=l, {t=:"&<", m, []=a}, {_, _, _}=r|rest] -> __parse__([{t, m, [l,r|a]}|rest])
[{_, _, _}=l, {t=:"<<", m, []=a}, {_, _, _}=r|rest] -> __parse__([{t, m, [l,r|a]}|rest])
[{_, _, _}=l, {t=:as, m, []=a}, {_, _, _}=r|rest] -> __parse__([{t, m, [l,r|a]}|rest])
[{_, _, _}=l, {t=:over, m, []=a}, {_, _, _}=r|rest] -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, _}=l, {t=:notnull, m, []=a}|rest] when tl in @literal -> __parse__([{t, m, [l|a]}|rest])
[{tl, _, _}=l, {t=:isnull, m, []=a}|rest] when tl in @literal -> __parse__([{t, m, [l|a]}|rest])
[{tl, _, _}=l, {t=:in, m, []=a}, {tr, _, _}=r|rest] when tl in @literal and tr in @literal -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, _}=l, {t=:like, m, []=a}, {ll, _, _}=lll, {et=:escape, em, []=ea}, {tr, _, _}=r|rest] when tl in @literal and ll in @literal and tr in @literal -> __parse__([{t, m, [l,{et, em, [lll, r|ea]}|a]}|rest])
[{tl, _, _}=l, {t=:ilike, m, []=a}, {ll, _, _}=lll, {et=:escape, em, []=ea}, {tr, _, _}=r|rest] when tl in @literal and ll in @literal and tr in @literal -> __parse__([{t, m, [l,{et, em, [lll, r|ea]}|a]}|rest])
[{tl, _, _}=l, {t=:like, m, []=a}, {tr, _, _}=r|rest] when tl in @literal and tr in @literal -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, _}=l, {t=:ilike, m, []=a}, {tr, _, _}=r|rest] when tl in @literal and tr in @literal -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, _}=l, {nt=:not, nm, []=na}, {t=:like, m, []=a}, {ll, _, _}=lll, {et=:escape, em, []=ea}, {tr, _, _}=r|rest] when tl in @literal and ll in @literal and tr in @literal -> __parse__([{t, m, [{nt, nm, [l|na]},{et, em, [lll, r|ea]}|a]}|rest])
[{tl, _, _}=l, {nt=:not, nm, []=na}, {t=:ilike, m, []=a}, {ll, _, _}=lll, {et=:escape, em, []=ea}, {tr, _, _}=r|rest] when tl in @literal and ll in @literal and tr in @literal -> __parse__([{t, m, [{nt, nm, [l|na]},{et, em, [lll, r|ea]}|a]}|rest])
[{tl, _, _}=l, {nt=:not, nm, []=na}, {t=:in, m, []=a}, {tr, _, _}=r|rest] when tl in @literal and tr in @literal -> __parse__([{t, m, [{nt, nm, [l|na]},r|a]}|rest])
[{tl, _, _}=l, {nt=:not, nm, []=na}, {t=:like, m, []=a}, {tr, _, _}=r|rest] when tl in @literal and tr in @literal -> __parse__([{t, m, [{nt, nm, [l|na]},r|a]}|rest])
[{tl, _, _}=l, {nt=:not, nm, []=na}, {t=:ilike, m, []=a}, {tr, _, _}=r|rest] when tl in @literal and tr in @literal -> __parse__([{t, m, [{nt, nm, [l|na]},r|a]}|rest])
[{tl, _, _}=l, {t=:is, m, []=a}, {false, _, _}=r|rest] when tl in @literal -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, _}=l, {t=:is, m, []=a}, {true, _, _}=r|rest] when tl in @literal -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, _}=l, {t=:is, m, []=a}, {:null, _, _}=r|rest] when tl in @literal -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, _}=l, {t=:is, m, []=a}, {:unknown, _, _}=r|rest] when tl in @literal -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, _}=l, {t=:is, m, []=a}, {:binding, _, _}=r|rest] when tl in @literal -> __parse__([{t, m, [l,r|a]}|rest])
[{tl, _, _}=l, {t=:is, m, []=a}, {nt=:not, nm, []=na}, {:distinct=d,dm,[]=da}, {:from=f,fm,[]=fa}, {tr, _, _}=r|rest] when tl in @literal and tr in @literal -> __parse__([{t, m, [l, {nt, nm, [{d,dm,[{f,fm,[r|fa]}|da]}|na]}|a]}|rest])
[{tl, _, _}=l, {t=:is, m, []=a}, {:distinct=d,dm,[]=da}, {:from=f,fm,[]=fa}, {tr, _, _}=r|rest] when tl in @literal and tr in @literal -> __parse__([{t, m, [l,{d,dm,[{f,fm,[r|fa]}|da]}|a]}|rest])
[{tl, _, _}=l, {t=:between, m, []=a}, {:asymmetric=d,dm,[]=da}, {ll, _, _}=lll, {:and=aa,am,[]=aaa}, {tr, _, _}=r|rest] when tl in @literal and ll in @literal and tr in @literal -> __parse__([{t, m, [l, {d,dm,[{aa,am,[lll,r|aaa]}|da]}|a]}|rest])
[{tl, _, _}=l, {t=:between, m, []=a}, {:symmetric=d,dm,[]=da}, {ll, _, _}=lll, {:and=aa,am,[]=aaa}, {tr, _, _}=r|rest] when tl in @literal and ll in @literal and tr in @literal -> __parse__([{t, m, [l, {d,dm,[{aa,am,[lll,r|aaa]}|da]}|a]}|rest])
[{tl, _, _}=l, {t=:between, m, []=a}, {ll, _, _}=lll, {:and=aa,am,[]=aaa}, {tr, _, _}=r|rest] when tl in @literal and ll in @literal and tr in @literal -> __parse__([{t, m, [l, {aa,am,[lll,r|aaa]}|a]}|rest])
[{tl, _, _}=l, {nt=:not, nm, []=na}, {t=:in, m, []=a}, {tr, _, _}=r|rest] when tl in @literal and tr in @literal -> __parse__([{t, m, [{nt, nm, [l|na]},r|a]}|rest])
[{tl, _, _}=l, {nt=:not, nm, []=na}, {t=:is, m, []=a}, {false, _, _}=r|rest] when tl in @literal -> __parse__([{t, m, [{nt, nm, [l|na]},r|a]}|rest])
[{tl, _, _}=l, {nt=:not, nm, []=na}, {t=:is, m, []=a}, {true, _, _}=r|rest] when tl in @literal -> __parse__([{t, m, [{nt, nm, [l|na]},r|a]}|rest])
[{tl, _, _}=l, {nt=:not, nm, []=na}, {t=:is, m, []=a}, {:null, _, _}=r|rest] when tl in @literal -> __parse__([{t, m, [{nt, nm, [l|na]},r|a]}|rest])
[{tl, _, _}=l, {nt=:not, nm, []=na}, {t=:is, m, []=a}, {:unknown, _, _}=r|rest] when tl in @literal -> __parse__([{t, m, [{nt, nm, [l|na]},r|a]}|rest])
[{tl, _, _}=l, {nt=:not, nm, []=na}, {t=:is, m, []=a}, {:binding, _, _}=r|rest] when tl in @literal -> __parse__([{t, m, [{nt, nm, [l|na]},r|a]}|rest])
[{tl, _, _}=l, {nt=:not, nm, []=na}, {t=:between, m, []=a}, {:asymmetric=d,dm,[]=da}, {ll, _, _}=lll, {:and=aa,am,[]=aaa}, {tr, _, _}=r|rest] when tl in @literal and ll in @literal and tr in @literal -> __parse__([{t, m, [{nt, nm, [l|na]}, {d,dm,[{aa,am,[lll,r|aaa]}|da]}|a]}|rest])
[{tl, _, _}=l, {nt=:not, nm, []=na}, {t=:between, m, []=a}, {:symmetric=d,dm,[]=da}, {ll, _, _}=lll, {:and=aa,am,[]=aaa}, {tr, _, _}=r|rest] when tl in @literal and ll in @literal and tr in @literal -> __parse__([{t, m, [{nt, nm, [l|na]}, {d,dm,[{aa,am,[lll,r|aaa]}|da]}|a]}|rest])
[{tl, _, _}=l, {nt=:not, nm, []=na}, {t=:between, m, []=a}, {ll, _, _}=lll, {:and=aa,am,[]=aaa}, {tr, _, _}=r|rest] when tl in @literal and ll in @literal and tr in @literal -> __parse__([{t, m, [{nt, nm, [l|na]}, {aa,am,[lll,r|aaa]}|a]}|rest])
[{t=:array, m, []=a}, {:bracket, _, _}=r|rest] -> __parse__([{t, m, [r|a]}|rest])
[{_,[_,{_, :literal}|_],_}=l,{_,[_,{_, :literal}|_],_}=r|rest] -> __parse__([{:as, [], [l,r]}|rest])
[{:dot, _, _}=l, {:ident, _, _}=r|rest] -> __parse__([{:as, [], [l,r]}|rest])
[{:ident, _, _}=l, {:ident, _, _}=r|rest] -> __parse__([{:as, [], [l,r]}|rest])
tokens -> tokens
end
end
@order %{insert: 0, delete: 0, update: 0, select: 0, set: 1, from: 1, join: 2, left: 2, right: 2, inner: 2, natural: 2, full: 2, cross: 2, where: 3, group: 4, having: 5, window: 6, order: 7, limit: 8, offset: 9, fetch: 10, returning: 11}
defp sort(acc), do: Enum.sort_by(acc, fn {tag, _, _} -> Map.get(@order, tag) end, :asc)
defp validate(_, %{validate: nil}, errors), do: errors
defp validate({tag, _, values}, %{validate: fun}, errors) when tag in ~w[select set having where on by order group returning insert into]a do
case validate_columns(fun, values, []) do
[] -> errors
e -> e++errors
end
end
defp validate({tag, _, values}, %{validate: fun} = context, errors) when tag in ~w[delete update from join]a do
values
|> Enum.reduce([], fn
{:on, _, _} = node, acc -> validate(node, context, acc)
{tag, _, _}=node, acc when tag in ~w[ident double_quote]a -> validate_table(fun, node, acc)
{:as, _, [{tag, _, _}=node, _]}, acc when tag in ~w[ident double_quote]a -> validate_table(fun, node, acc)
{:dot, _, [_, {tag, _, _}=node]}, acc when tag in ~w[ident double_quote]a -> validate_table(fun, node, acc)
{:dot, _, [_, {:bracket, _, [{:ident, _, _}=node]}]}, acc -> validate_table(fun, node, acc)
{:comma, _, [{:dot, _, [_, {:bracket, _, [{:ident, _, _}=node]}]}]}, acc -> validate_table(fun, node, acc)
{:comma, _, [{:dot, _, [_, {tag, _, _}=node]}]}, acc when tag in ~w[ident double_quote]a -> validate_table(fun, node, acc)
{:comma, _, [{:as, _, [{tag, _, _}=node, _]}]}, acc when tag in ~w[ident double_quote]a -> validate_table(fun, node, acc)
{:comma, _, [{tag, _, _}=node|_]}, acc when tag in ~w[ident double_quote]a -> validate_table(fun, node, acc)
_, acc -> acc
end)
|> case do
[] -> errors
values -> values++errors
end
end
defp validate({tag, _, [{t, _, _}]}, _, errors) when tag in ~w[offset limit]a and t in ~w[numeric binary hexadecimal octal]a, do: errors
defp validate({tag, _, _} = node, _, errors) when tag in ~w[offset limit]a, do: [node|errors]
defp validate_table(fun, {_, _, value}=node, acc) do
case fun.(value, nil) do
true -> acc
false -> [node|acc]
end
end
defp validate_columns(fun, {tag, _, value}=node, acc) when tag in ~w[ident double_quote]a do
case fun.(nil, value) do
true -> acc
false -> [node|acc]
end
end
defp validate_columns(fun, {:as, _, [left, _]}, acc), do: validate_columns(fun, left, acc)
defp validate_columns(fun, {_, _, values}, acc), do: validate_columns(fun, values, acc)
defp validate_columns(fun, [node|values], acc) do
validate_columns(fun, values, validate_columns(fun, node, acc))
end
defp validate_columns(_fun, _, acc), do: acc
defp description([{tag, _, [left, right]}|_rest], columns, froms, select, bindings) when tag in ~w[except intersect union]a do
left = description(List.wrap(left), columns, froms, select, bindings)
right = description(List.wrap(right), columns, froms, select, bindings)
if left == right, do: left, else: right
end
defp description([{:paren, _, [[_|_]=values]}|rest], columns, froms, select, bindings) do
description(values++rest, columns, froms, select, bindings)
end
defp description([{tag, _, _}=node|rest], columns, froms, select, bindings) when tag in ~w[insert from update inner outer left right full natural cross join]a do
description(rest, columns, [node|froms], select, [node|bindings])
end
defp description([{tag, _, _}=node|rest], columns, froms, select, bindings) when tag in ~w[select returning]a do
description(rest, columns, froms, [node|select], [node|bindings])
end
defp description([{_, _, _}=node|rest], columns, froms, select, bindings) do
description(rest, columns, froms, select, [node|bindings])
end
defp description([[{_, _, _}|_]=rest], columns, froms, select, bindings) do
description(rest, columns, froms, select, bindings)
end
defp description([], columns, []=aliases, [], bindings) do
{types, params} = resolve_binding(Enum.reverse(bindings), aliases, columns, [], [])
{:ok, [], 0, Enum.reverse(types), Enum.reverse(params)}
end
defp description([], columns, froms, select, bindings) do
aliases = resolve_aliases(froms, columns, [])
description = resolve_description(select, aliases, columns, [])
{types, params} = resolve_binding(Enum.reverse(bindings), aliases, columns, [], [])
{:ok, description, length(description), Enum.reverse(types), params}
end
defp resolve_description([], _aliases, _columns, []=acc), do: acc
defp resolve_description([], _aliases, _columns, acc), do: List.last(acc)
defp resolve_description([node|rest], aliases, columns, acc) do
resolve_description(rest, aliases, columns, [resolve_desc(node, columns, aliases)|acc])
end
defp resolve_desc({:select, _, [{:distinct, _, _}|values]}, columns, aliases) do
resolve_desc(values, aliases, columns, [])
end
defp resolve_desc({tag, _, values}, columns, aliases) when tag in ~w[select returning]a do
resolve_desc(values, aliases, columns, [])
end
defp resolve_desc([], _aliases, _columns, acc), do: Enum.reverse(acc)
defp resolve_desc([{:*, _, []}], aliases, _columns, []), do: Enum.flat_map(aliases, fn {_, [value]} ->
case elem(value, tuple_size(value)-1) do
[{_, [_|_]=cols}] -> cols
cols -> cols
end
end)
defp resolve_desc([{:comma, _, [node]}|rest], aliases, columns, acc) do
resolve_desc(rest, aliases, columns, [resolve_column(node, aliases, columns)|acc])
end
defp resolve_desc([{_,_,_}=node|rest], aliases, columns, acc) do
resolve_desc(rest, aliases, columns, [resolve_column(node, aliases, columns)|acc])
end
defp find_column([], _column), do: nil
defp find_column([{_, column}=type|_], column), do: type
defp find_column([_|rest], column), do: find_column(rest, column)
defp columns(_table, []=columns), do: columns
defp columns(_table, [from: _values]) do
[]
end
defp columns(table, columns) do
table = "#{table}"
columns
|> Enum.filter(&(&1.table_name == table))
|> Enum.sort_by(& &1.ordinal_position)
|> Enum.map(fn col -> {:"#{col.udt_name}", :"#{col.column_name}"} end)
end
defp columns(_schema, _table, []=columns), do: columns
defp columns(schema, table, columns) do
schema = "#{schema}"
table = "#{table}"
columns
|> Enum.filter(&(&1.table_schema == schema and &1.table_name == table))
|> Enum.sort_by(& &1.ordinal_position)
|> Enum.map(fn col -> {:"#{col.udt_name}", :"#{col.column_name}"} end)
end
defp resolve_aliases([{tag, _, value}|rest], columns, aliases) when tag in ~w[insert from update delete inner outer left right full natural cross join]a do
resolve_aliases(rest, columns, [{tag, resolve_aliases(value, columns, [])}|aliases])
end
defp resolve_aliases([{:into, _, [{:ident,_,table}|_]}|rest], columns, aliases) do
resolve_aliases(rest, columns, [{:"#{table}", columns(table, columns)}|aliases])
end
defp resolve_aliases([{:as, _, [{:ident,_,table},{:ident,_,as}]}|rest], columns, aliases) do
resolve_aliases(rest, columns, [{:"#{table}", :"#{as}", columns(table, columns)}|aliases])
end
defp resolve_aliases([{:as, _, [{:dot,_,[{:ident,_,schema},{:ident,_,table}]},{:ident,_,as}]}|rest], columns, aliases) do
resolve_aliases(rest, columns, [{:"#{schema}", :"#{table}", :"#{as}", columns(schema, table, columns)}|aliases])
end
defp resolve_aliases([{:ident,_,table}|rest], columns, aliases) do
resolve_aliases(rest, columns, [{:"#{table}", columns(table, columns)}|aliases])
end
defp resolve_aliases([{:dot,_,[{:ident,_,schema},{:ident,_,table}]}|rest], columns, aliases) do
resolve_aliases(rest, columns, [{:"#{schema}", :"#{table}", columns(schema, table, columns)}|aliases])
end
defp resolve_aliases([{:paren,_, _value}, {:ident,_,as}|rest], columns, aliases) do
resolve_aliases(rest, columns, [{:"#{as}", []}|aliases])
end
defp resolve_aliases([{:paren,_, _value}|rest], columns, aliases) do
resolve_aliases(rest, columns, [{:paren, []}|aliases])
end
defp resolve_aliases([_|rest], columns, aliases) do
resolve_aliases(rest, columns, aliases)
end
defp resolve_aliases([], _columns, aliases), do: aliases
defp resolve_type([{tag, values}|rest], column, prospect) when tag in ~w[from join update]a do
resolve_type(rest, column, Enum.flat_map(values, &List.wrap(find_column(elem(&1, tuple_size(&1)-1), column)))++prospect)
end
defp resolve_type([{_tag, values}|rest], column, prospect) do
resolve_type(rest, column, resolve_type(values, column, prospect))
end
defp resolve_type([], _column, prospect), do: prospect
defp resolve_binding([], [], types, params), do: {types, params}
defp resolve_binding([{:binding, _, [value]}|values], [{type, _col}|aliases], types, params) do
resolve_binding(values, aliases, [type|types], [value|params])
end
defp resolve_binding([{:comma, _, [{:binding, _, [value]}]}|values], [{type, _col}|aliases], types, params) do
resolve_binding(values, aliases, [type|types], [value|params])
end
defp resolve_binding([_|values], [_|aliases], types, params) do
resolve_binding(values, aliases, types, params)
end
defp resolve_binding([{:into, _, [_, {:paren,_, c}, {:values,_,[{:paren, _, values}]}]}|rest], [insert: [insert]]=aliases, columns, types, params) do
{types, params} = resolve_binding(values, resolve_values(c, elem(insert, tuple_size(insert)-1), []), types, params)
resolve_binding(rest, aliases, columns, types, params)
end
defp resolve_binding([{:as, _, [{:binding, _, [value]}, _right]}|rest], aliases, columns, types, params) do
resolve_binding(rest, aliases, columns, [nil|types], [value|params])
end
defp resolve_binding([{_, [_, {:type, :operator}|_], [{:not, _, [{:binding, _, [l]}]}, {:binding, _, [r]}]}|rest], aliases, columns, types, params) do
resolve_binding(rest, aliases, columns, [nil,nil|types], [r,l|params])
end
defp resolve_binding([{_, [_, {:type, :operator}|_], [{:binding, _, [l]}, {:binding, _, [r]}]}|rest], aliases, columns, types, params) do
resolve_binding(rest, aliases, columns, [nil,nil|types], [r,l|params])
end
defp resolve_binding([{_, [_, {:type, :operator}|_], [{:binding, _, [value]}, right]}|rest], aliases, columns, types, params) do
case type(right, aliases) do
[{type, _col}] -> resolve_binding(rest, aliases, columns, [type|types], [value|params])
type -> resolve_binding(rest, aliases, columns, [type|types], [value|params])
end
end
defp resolve_binding([{_, [_, {:type, :operator}|_], [left, {:binding, _, [value]}]}|rest], aliases, columns, types, params) do
case type(left, aliases) do
[{type, _col}] -> resolve_binding(rest, aliases, columns, [type|types], [value|params])
type -> resolve_binding(rest, aliases, columns, [type|types], [value|params])
end
end
defp resolve_binding([{:binding, _, [value]}|rest], []=aliases, columns, types, params) do
resolve_binding(rest, aliases, columns, [nil|types], [value|params])
end
defp resolve_binding([{_, _, [{_, _, _}|_]=value}|rest], aliases, columns, types, params) do
{types, params} = resolve_binding(value, aliases, columns, types, params)
resolve_binding(rest, aliases, columns, types, params)
end
defp resolve_binding([{_, _, _}|rest], aliases, columns, types, params) do
resolve_binding(rest, aliases, columns, types, params)
end
defp resolve_binding([], _aliases, _columns, types, params) do
{types, params}
end
defp resolve_values([], _columns, acc), do: Enum.reverse(acc)
defp resolve_values([{:ident, _, ident}|rest], columns, acc) do
resolve_values(rest, columns, [find_column(columns, :"#{ident}")|acc])
end
defp resolve_values([{:comma, _, [{:ident, _, ident}]}|rest], columns, acc) do
resolve_values(rest, columns, [find_column(columns, :"#{ident}")|acc])
end
defp type({:paren, _, [{:select, _, _}|_]}, aliases), do: {:record, aliases}
defp type({:over, _, [{:rank, _, _}, _]}, _aliases), do: :numeric
defp type({tag, _, [_, right]}, aliases) when tag in ~w[:: dot]a, do: type(right, aliases)
defp type({:array_agg, _, [type|_]}, aliases) do
case resolve_column(type, aliases, []) do
{type, _} -> {:array, type}
type -> {:array, type}
end
end
defp type({:bracket, _, [type|_]}, aliases), do: {:array, type(type, aliases)}
defp type({:quote, _, _}, _aliases), do: :text
defp type({tag, _, _}, _aliases) when tag in ~w[true false]a, do: :bool
defp type({tag, _, _}, _aliases) when tag in ~w[numeric avg - + *]a, do: :numeric
defp type({tag, _, _}, _aliases) when tag in ~w[null hstore]a, do: tag
defp type({:ident, _, ident}, aliases) do
col = :"#{ident}"
case resolve_type(aliases, col, []) do
[] -> col
type -> type
end
end
defp type({type, _, []}, _aliases), do: type
defp type({_, _, [{:paren, _, _}]}, _aliases), do: :void
defp type({_, _, _}, []=aliases), do: {:record, aliases}
defp type(_, _aliases), do: nil
defp column({:ident, _, col}, _aliases), do: :"#{col}"
defp column({col, _, []}, _aliases), do: col
defp column({:dot, _, [_, right]}, aliases), do: column(right, aliases)
defp column({:comma, _, [right]}, aliases), do: column(right, aliases)
defp column({_tag, _, [{:paren, _, [left, right]}]}, aliases), do: column(left, aliases) || column(right, aliases)
defp column(_, _), do: nil
defp resolve_column({:as, _, [left, right]}, aliases, _columns), do: {type(left, aliases), column(right, aliases)}
defp resolve_column({:"::", _, [{:paren, _, _} = _left, {:dot, _, [{:ident, _, schema}, {:ident, _, table}]}]}, aliases, _columns), do: columns(schema, table, aliases)
defp resolve_column({:"::", _, [{t, _, _}, right]}, aliases, _columns) when t in ~w[binding paren numeric quote]a, do: {type(right, aliases), nil}
defp resolve_column({:"::", _, [left, right]}, aliases, _columns), do: {type(right, aliases), column(left, aliases)}
defp resolve_column({:dot, _, [{:dot, _, [{:ident, _, schema}, {:ident, _, table}]}, {:*, _, []}]}, aliases, _columns), do: columns(schema, table, aliases)
defp resolve_column({:dot, _, [{:ident, _, table}, {:*, _, []}]}, aliases, _columns), do: columns(table, aliases)
defp resolve_column({:=, _, [left, right]}, aliases, _columns), do: {:bool, column(left, aliases) || column(right, aliases)}
defp resolve_column({:coalesce, _, [{:paren, _, [left, right]}]}, aliases, _columns), do: column(left, aliases) || column(right, aliases)
defp resolve_column({tag, _, [type|_]}, aliases, columns) when tag in ~w[array_agg bracket]a, do: {:array, resolve_column(type, aliases, columns)}
defp resolve_column({:array, _, [type]}, aliases, columns), do: resolve_column(type, aliases, columns)
defp resolve_column({:paren, _, [{:select, _, _}|_] = values}, aliases, columns) do
{:record, resolve_desc(values, aliases, columns, [])}
end
defp resolve_column({:paren, _, [left|_]}, aliases, columns), do: resolve_column(left, aliases, columns)
defp resolve_column({:select, _, [{:ident, _, _}, {:paren, _, _}]}, _aliases, _columns) do
{:record, [:void]}
end
defp resolve_column({:row, _, [{:paren, _, values}]}, aliases, columns) do
{:record, resolve_desc(values, aliases, columns, [])}
end
defp resolve_column(node, aliases, _columns) do
type = type(node, aliases)
column = column(node, aliases)
case type == column do
true -> {nil, column}
false -> {type, column}
end
end
end