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
sql lib parser.ex
Raw

lib/parser.ex

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2025 DBVisor
defmodule SQL.Parser do
@moduledoc false
@compile {:inline, validate: 3, parse: 7, __parse__: 2}
def parse(tokens, context) do
parse(tokens, context, [], [], [], [], [])
end
def parse([], context, [], [], [], tokens, errors) do
{:ok, Map.update!(context, :errors, &:lists.flatten(errors, &1)), tokens}
end
def parse([], context, [], [], acc, tokens, errors) do
{:ok, Map.update!(context, :errors, &:lists.flatten(errors, &1)), :lists.flatten(sort(acc), tokens)}
end
def parse([], context, a, acc, [], [], errors) do
{a, context} = __parse__(a, context)
{:ok, Map.update!(context, :errors, &:lists.flatten(errors, &1)), [a|acc]}
end
def parse([], context, unit, acc, root, [], errors) do
{:ok, Map.update!(context, :errors, &:lists.flatten(errors, &1)), :lists.flatten([unit|acc], root)}
end
def parse([{:with=t, m, []=unit}|tokens], context, a, acc, root, acc2, errors) do
{a, context} = __parse__(a, context)
parse(tokens, context, unit, unit, [{t, m, [:lists.flatten(a, acc),sort(root)|unit]}], acc2, errors)
end
def parse([{:comma, _, []} = node|tokens], context, a, acc, root, acc2, errors) do
{a, context} = __parse__(a, context)
parse(tokens, context, [node], :lists.flatten(a, acc), root, acc2, errors)
end
def parse([{:colon=t, tm, ta}|tokens], context, []=unit, []=acc, root, acc2, errors) do
{:ok, context, ta} = parse(ta, context)
parse(tokens, context, unit, acc, [], [{t, tm, ta},sort(root)|acc2], errors)
end
def parse([l,{t,tm,[]=ta},r,{t3,t3m,t3a},{:fetch=f,fm,[]=fa}|tokens], context, []=unit, []=acc, root, acc2, errors) when t in ~w[from in]a and t3 in ~w[backward forward]a do
parse(tokens, context, unit, acc, [{f,fm,[{t3,t3m,[r|t3a]},{t,tm,[l|ta]}|fa]}|root], acc2, errors)
end
def parse([l,{t,tm,[]=ta},r,{_,[_,{:tag, t3}|_]=t3m,_},{:fetch=f,fm,[]=fa}|tokens], context, []=unit, []=acc, root, acc2, errors) when t in ~w[from in]a and t3 in ~w[absolute relative]a do
parse(tokens, context, unit, acc, [{f,fm,[{t3,t3m,[r]},{t,tm,[l|ta]}|fa]}|root], acc2, errors)
end
def parse([r,{t,tm,[]=ta},{:ident,[_,{:tag, l}|_]=lm,_},{:fetch=f,fm,[]=fa}|tokens], context, []=unit, []=acc, root, acc2, errors) when t in ~w[from in]a do
parse(tokens, context, unit, acc, [{f,fm,[{l,lm,[{t,tm,[r|ta]}]}|fa]}|root], acc2, errors)
end
def parse([r,{t,tm,[]=ta},{:numeric,_,_}=l,{:fetch=f,fm,[]=fa}|tokens], context, []=unit, []=acc, root, acc2, errors) when t in ~w[from in]a do
parse(tokens, context, unit, acc, [{f,fm,[l,{t,tm,[r|ta]}|fa]}|root], acc2, errors)
end
def parse([r,{t,tm,[]=ta},{l,lm,la},{:fetch=f,fm,[]=fa}|tokens], context, []=unit, []=acc, root, acc2, errors) when t in ~w[from in]a do
parse(tokens, context, unit, acc, [{f,fm,[{l,lm, [{t,tm,[r|ta]}|la]}|fa]}|root], acc2, errors)
end
def parse([{t,m,[]=unit}|tokens], context, a, acc, root, acc2, errors) when t in ~w[by on]a do
{a, context} = __parse__(a, context)
parse(tokens, context, unit, [{t,m,:lists.flatten(a, acc)}], root, acc2, errors)
end
def parse([{_,[_,{:tag,t}|_]=m,_}, node|tokens], context, unit, acc, root, acc2, errors) when t in ~w[asc desc]a do
parse(tokens, context, [{t,m,[node]}|unit], acc, root, acc2, errors)
end
def 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
def 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
def parse([{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 and a in ~w[all distinct]a do
{:ok, context, la} = parse(la, context)
parse(tokens, context, unit, acc, [{t,tm,[{l, lm, la},{a, am, [sort(root)|aa]}|ta]}], acc2, errors)
end
def 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)
parse(tokens, context, unit, acc, [{t,tm,[{r, rm, ra}, sort(root)|ta]}], acc2, errors)
end
def parse([{a, am, []=aa}, {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
{:ok, context, la} = parse(tokens, context)
parse([], context, unit, acc, [{t,tm,[la,{a,am,[sort(root)|aa]}|ta]}], acc2, errors)
end
def 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)
parse([], context, unit, acc, [{t,tm,[la, sort(root)|ta]}], acc2, errors)
end
def 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
{a, context} = __parse__(a, context)
node = {t,m,:lists.flatten(a, acc)}
parse(tokens, context, unit, unit, [{n, nm, [{l, lm, [{o, om, [node|oa]}|la]}|na]}|root], acc2, validate(node, context, errors))
end
def 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
{a, context} = __parse__(a, context)
node = {t,m,:lists.flatten(a, acc)}
parse(tokens, context, unit, unit, [{l, lm, [{o, om, [node|oa]}|la]}|root], acc2, validate(node, context, errors))
end
def 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
{a, context} = __parse__(a, context)
node = {t,m,:lists.flatten(a, acc)}
parse(tokens, context, unit, unit, [{n, nm, [{i, im, [node|ia]}|na]}|root], acc2, validate(node, context, errors))
end
def 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
{a, context} = __parse__(a, context)
node = {t,m,:lists.flatten(a, acc)}
parse(tokens, context, unit, unit, [{l, lm, [node|la]}|root], acc2, validate(node, context, errors))
end
def 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
{a, context} = __parse__(a, context)
node = {t,m,:lists.flatten(a, acc)}
parse(tokens, context, unit, unit, [node|root], acc2, validate(node, context, errors))
end
def parse([{:from=t,m,[]=unit}|tokens], context, [{tag,_,_}|_]=a, acc, root, acc2, errors) when tag in ~w[double_quote bracket dot ident as binding]a do
{a, context} = __parse__(a, context)
node = {t,m,:lists.flatten(a, acc)}
parse(tokens, context, unit, unit, [node|root], acc2, validate(node, context, errors))
end
def parse([{t,m,[]=unit}|tokens], context, a, acc, root, acc2, errors) when t in ~w[select where group having order limit offset]a do
{a, context} = __parse__(a, context)
parse(tokens, context, unit, unit, [{t,m,:lists.flatten(a, acc)}|root], acc2, errors)
end
def parse([{t,m,[{:paren=t2,m2,a2}]}|tokens], context, unit, acc, root, acc2, errors) do
{:ok, context, a2} = parse(a2, context)
parse(tokens, context, [{t,m,[{t2,m2,a2}]}|unit], acc, root, acc2, errors)
end
def parse([{t,m,[r, {:paren=t2,m2,a2}]}|tokens], context, unit, acc, root, acc2, errors) do
{:ok, context, a2} = parse(a2, context)
parse(tokens, context, [{t,m,[r, {t2,m2,a2}]}|unit], acc, root, acc2, errors)
end
def parse([{:paren=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
def 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,:lists.flatten(unit, acc)}|ac]}|root], acc2, errors)
end
def parse([{tag,_,_}=node|tokens], context, unit, acc, root, acc2, errors) when tag in ~w[numeric ident quote double_quote backtick bracket dot binding]a do
parse(tokens, context, [node|unit], acc, root, acc2, errors)
end
def parse([node|tokens], context, unit, acc, root, acc2, errors) do
parse(tokens, context, [node|unit], acc, root, acc2, errors)
end
def __parse__([l,{t,m,a},r|[{c,_,[]}|_]=rest],context) when c in ~w[and or]a, do: __parse__([{t,m,[l,r|a]}|rest],context)
def __parse__([b,{c,cm,[]=ca},l,{t,m,a},r|[{c2,_,[]}|_]=rest],context) when c in ~w[and or]a and c2 in ~w[and or]a, do: __parse__([{c,cm,[b,{t,m,[l,r|a]}|ca]}|rest],context)
def __parse__([b,{c,cm,[]=ca},l,{t,m,a},r|rest],context) when c in ~w[and or]a, do: __parse__([{c,cm,[b,{t,m,[l,r|a]}|ca]}|rest],context)
def __parse__([{c,_,[_, _]}=l,{c2,c2m,[]=c2a},r|rest],context) when c in ~w[and or]a and c2 in ~w[and or]a, do: __parse__([{c2,c2m,[l,r|c2a]}|rest],context)
def __parse__([b,{c,cm,[]=ca}|rest],context) when c in ~w[notnull isnull]a, do: __parse__([{c,cm,[b|ca]}|rest],context)
def __parse__([b,{:not=c,cm,[]=ca},{:between=n,nm,[]=na},{d,dm,[]=da},l,{:and=f,fm,[]=fa},r|rest],context) when d in ~w[asymmetric symmetric]a, do: __parse__([{n,nm,[{c,cm,[b|ca]},{d,dm,[{f,fm,[l,r|fa]}|da]}|na]}|rest],context)
def __parse__([b,{:not=c,cm,[]=ca},{:between=n,nm,[]=na},l,{:and=f,fm,[]=fa},r|rest],context), do: __parse__([{n,nm,[{c,cm,[b|ca]},{f,fm,[l,r|fa]}|na]}|rest],context)
def __parse__([b,{:between=n,nm,[]=na},{d,dm,[]=da},l,{:and=f,fm,[]=fa},r|rest],context) when d in ~w[asymmetric symmetric]a, do: __parse__([{n,nm,[b,{d,dm,[{f,fm,[l,r|fa]}|da]}|na]}|rest],context)
def __parse__([b,{:between=n,nm,[]=na},l,{:and=f,fm,[]=fa},r|rest],context), do: __parse__([{n,nm,[b,{f,fm,[l,r|fa]}|na]}|rest],context)
def __parse__([b,{:is=c,cm,[]=ca},{:not=n,nm,[]=na},{:distinct=d,dm,[]=da},{:from=f,fm,[]=fa},node|rest],context), do: __parse__([{c,cm,[b,{n,nm,[{d,dm,[{f,fm,[node|fa]}|da]}|na]}|ca]}|rest],context)
def __parse__([b,{:is=c,cm,[]=ca},{:distinct=d,dm,[]=da},{:from=f,fm,[]=fa},node|rest],context), do: __parse__([{c,cm,[b,{d,dm,[{f,fm,[node|fa]}|da]}|ca]}|rest],context)
def __parse__([b,{:is=c,cm,[]=ca},{:not=n,nm,[]=na},{t,_,[]}=node|rest],context) when t in ~w[false true unknown null binding]a, do: __parse__([{c,cm,[b,{n,nm,[node|na]}|ca]}|rest],context)
def __parse__([b,{:is=c,cm,[]=ca},{t,_,[]}=node|rest],context) when t in ~w[false true unknown null binding]a, do: __parse__([{c,cm,[b,node|ca]}|rest],context)
def __parse__([b,{:not=n,nm,[]=na},{:in=c,cm,[]=ca},node|rest],context), do: __parse__([{n,nm,[b,{c,cm,[node|ca]}|na]}|rest],context)
def __parse__([b,{:in=c,cm,[]=ca},node|rest],context), do: __parse__([{c,cm,[b,node|ca]}|rest],context)
def __parse__([b,{c,cm,[]=ca},n|rest],context) when c in ~w[as ilike like <= >= < > <> / * + - =]a, do: __parse__([{c,cm,[b,n|ca]}|rest],context)
def __parse__([{t,m,[]=a},b,{:as=c,cm,[]=ca},n|rest],context), do: __parse__([{t,m,[{c,cm,[b,n|ca]}|a]}|rest],context)
def __parse__([{tl,_,a}=l,{tr,_,as}=r],context) when tl in ~w[ident double_quote bracket dot binding]a and tr in ~w[ident double_quote bracket dot binding]a, do: __parse__([{:as, [], [l,r]}], Map.update!(context, :aliases, &[{as, a}|&1]))
def __parse__([{tl,_,la}=l,{:as=tr,rm,ra}],context) when tl in ~w[ident double_quote bracket dot binding]a, do: __parse__([{:as=tr,rm,[l|ra]}], Map.update!(context, :aliases, &[{la, nil}|&1]))
def __parse__([l,{:comma=tc,mc,[]=ac}|unit],context), do: {[{tc,mc,[l|ac]}|unit], context}
def __parse__(unit, context), do: {unit, context}
@order %{select: 0, from: 1, join: 2, where: 3, group: 4, having: 5, window: 6, order: 7, limit: 8, offset: 9, fetch: 10}
def sort(acc), do: Enum.sort_by(acc, fn {tag, _, _} -> Map.get(@order, tag) end, :asc)
def validate(_, %{sql_lock: nil}, errors), do: errors
def validate({tag, _, _}, %{sql_lock: %{tables: []}}, errors) when tag in ~w[from join]a, do: errors
def validate({tag, _meta, values}, %{sql_lock: %{tables: tables}}, errors) when tag in ~w[from join]a do
values
|> Enum.reduce([], fn
{:paren, _, _}, acc -> acc
{:on, _, _}, acc -> acc
{tag, _, _}=node, acc when tag in ~w[ident double_quote]a -> validate_table(tables, node, acc)
{:as, _, [{tag, _, _}=node, _]}, acc when tag in ~w[ident double_quote]a -> validate_table(tables, node, acc)
{:dot, _, [_, {tag, _, _}=node]}, acc when tag in ~w[ident double_quote]a -> validate_table(tables, node, acc)
{:dot, _, [_, {:bracket, _, [{:ident, _, _}=node]}]}, acc -> validate_table(tables, node, acc)
{:comma, _, [{:dot, _, [_, {:bracket, _, [{:ident, _, _}=node]}]}]}, acc -> validate_table(tables, node, acc)
{:comma, _, [{:dot, _, [_, {tag, _, _}=node]}]}, acc when tag in ~w[ident double_quote]a -> validate_table(tables, node, acc)
{:comma, _, [{:as, _, [{tag, _, _}=node, _]}]}, acc when tag in ~w[ident double_quote]a -> validate_table(tables, node, acc)
{:comma, _, [{tag, _, _}=node]}, acc when tag in ~w[ident double_quote]a -> validate_table(tables, node, acc)
end)
|> case do
[] -> errors
values -> [values|errors]
end
end
def validate_table(tables, {_, _, value}=node, acc) do
case Enum.find(tables, false, fn %{table_name: {_, _, fun}} -> fun.(value) end) do
true -> acc
false -> [node|acc]
end
end
end