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/format.ex
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2025 DBVisor
defmodule SQL.Format do
@moduledoc false
@moduledoc since: "0.4.0"
@error IO.ANSI.red()
@reset IO.ANSI.reset()
@keyword IO.ANSI.magenta()
@literal IO.ANSI.yellow()
@enclosed IO.ANSI.green()
@compile {:inline, indention: 3, newline: 2, pad: 1}
@doc false
@doc since: "0.4.0"
def to_iodata(tokens, context, indent, color), do: pad(to_iodata(tokens, color, context.binding, context.case, context.errors, indent, []))
defp indention(acc, [{_, {_,_,_,_,_,0}}|_], _), do: acc
defp indention(acc, [{_, {_,_,_,_,_,0,_,_}}|_], _), do: acc
defp indention(acc, [{_, {_,_,_,_,_,_}}|_], 0), do: [?\s|acc]
defp indention(acc, [{_, {_,_,_,_,_,_,_,_}}|_], 0), do: [?\s|acc]
defp indention(acc, 0, 0), do: acc
defp indention(acc, _, 0), do: acc
defp indention(acc, _, 1), do: [?\s,?\s|acc]
defp indention(acc, m, indent), do: indention([?\s,?\s|acc], m, indent-1)
defp newline(acc, indent), do: [?\n|indention(acc, 0, indent)]
defp pad([?\n|_]=acc), do: acc
defp pad(acc), do: [?\n|acc]
newline = ~w[select from join where having window limit offset fetch]a
{reserved, non_reserved, operators} = SQL.BNF.get_rules()
for atom <- Enum.uniq(Enum.map(reserved++non_reserved++operators,&elem(&1, 0))), atom not in newline do
defp to_iodata(unquote(atom), true, _binding ,:lower, _errors, _indent, acc) do
[@keyword, unquote("#{atom}"), @reset|acc]
end
defp to_iodata(unquote(atom), true, _binding, :upper, _errors, _indent, acc) do
[@keyword, unquote(String.upcase("#{atom}")), @reset|acc]
end
defp to_iodata(unquote(atom), false, _binding ,:lower, _errors, _indent, acc) do
[unquote("#{atom}")|acc]
end
defp to_iodata(unquote(atom), false, _binding, :upper, _errors, _indent, acc) do
[unquote(String.upcase("#{atom}"))|acc]
end
end
for atom <- newline do
defp to_iodata({unquote(atom), _m, values}, true=color, binding, :lower=case, errors, indent, acc) do
newline([@keyword, unquote("#{atom}"), @reset|pad(to_iodata(values, color, binding, case, errors, indent+1, acc))], indent)
end
defp to_iodata({unquote(atom), _m, values}, true=color, binding, :upper=case, errors, indent, acc) do
newline([@keyword, unquote(String.upcase("#{atom}")), @reset|pad(to_iodata(values, color, binding, case, errors, indent+1, acc))], indent)
end
defp to_iodata({unquote(atom), _m, values}, false=color, binding, :lower=case, errors, indent, acc) do
newline([unquote("#{atom}")|pad(to_iodata(values, color, binding, case, errors, indent+1, acc))], indent)
end
defp to_iodata({unquote(atom), _m, values}, false=color, binding, :upper=case, errors, indent, acc) do
newline([unquote(String.upcase("#{atom}"))|pad(to_iodata(values, color, binding, case, errors, indent+1, acc))], indent)
end
end
for atom <- ~w[group order]a do
defp to_iodata({unquote(atom), _m, values}, true=color, binding, :lower=case, errors, indent, acc) do
newline([@keyword, unquote("#{atom}"), @reset|to_iodata(values, color, binding, case, errors, indent, acc)], indent)
end
defp to_iodata({unquote(atom), _m, values}, true=color, binding, :upper=case, errors, indent, acc) do
newline([@keyword, unquote(String.upcase("#{atom}")), @reset|to_iodata(values, color, binding, case, errors, indent, acc)], indent)
end
defp to_iodata({unquote(atom), _m, values}, false=color, binding, :lower=case, errors, indent, acc) do
newline([unquote("#{atom}")|to_iodata(values, color, binding, case, errors, indent, acc)], indent)
end
defp to_iodata({unquote(atom), _m, values}, false=color, binding, :upper=case, errors, indent, acc) do
newline([unquote(String.upcase("#{atom}"))|to_iodata(values, color, binding, case, errors, indent, acc)], indent)
end
end
defp to_iodata({tag, m, values}, color, binding, case, errors, indent, acc) when tag in ~w[by on]a do
indention(to_iodata(tag, color, binding, case, errors, 0, newline(to_iodata(values, color, binding, case, errors, indent, acc), indent)), m, 0)
end
defp to_iodata(:comma, _color, _binding, _case, _errors, 0, acc) do
[?,|acc]
end
defp to_iodata(:comma, _color, _binding, _case, _errors, _indent, acc) do
[?,|pad(acc)]
end
defp to_iodata(:dot, _color, _binding, _case, _errors, _indent, acc) do
[?.|acc]
end
defp to_iodata(:paren, _color, _binding, _case, _errors, _indent, acc) do
[?(,?)|acc]
end
defp to_iodata({:colon, _m, values}, color, binding, case, errors, indent, acc) do
to_iodata(values, color, binding, case, errors, indent, [?;|acc])
end
defp to_iodata({:binding, m, [idx]}, _color, binding, _case, _errors, indent, acc) do
indention([?{,?{,Macro.to_string(Enum.at(binding, idx-1)),?},?}|acc], m, indent)
end
defp to_iodata({:comment, _m, value}, _color, _binding, _case, _errors, _indent, acc) do
[?-,?-,value|acc]
end
defp to_iodata({:comments, _m, value}, _color, _binding, _case, _errors, _indent, acc) do
[?\\,?*,value,?*,?\\|acc]
end
defp to_iodata({:quote, m, value}, true, _binding, _case, _errors, indent, acc) do
indention([?',@enclosed, value, @reset,?'|acc], m, indent)
end
defp to_iodata({:quote, m, value}, false, _binding, _case, _errors, indent, acc) do
indention([?', value,?'|acc], m, indent)
end
defp to_iodata({:backtick, m, value}, true, _binding, _case, _errors, indent, acc) do
indention([?`,@enclosed, value, @reset,?`|acc], m, indent)
end
defp to_iodata({:backtick, m, value}, false, _binding, _case, _errors, indent, acc) do
indention([?`, value,?`|acc], m, indent)
end
defp to_iodata({tag, m, []}, color, binding, case, errors, indent, acc) do
indention(to_iodata(tag, color, binding, case, errors, indent, acc), m, indent)
end
defp to_iodata({:paren, m, [{t, _, _}|_]=values}, color, binding, case, errors, indent, acc) when t in unquote(newline++~w[group order union except intersect]a) do
indention([?(|to_iodata(values, color, binding, case, errors, indent+1, [?\n,?)|acc])], m, indent)
end
defp to_iodata({:paren, m, values}, color, binding, case, errors, indent, acc) do
indention([?(|to_iodata(values, color, binding, case, errors, 0, [?)|acc])], m, indent)
end
defp to_iodata({:bracket, m, values}, color, binding, case, errors, indent, acc) do
indention([?[|to_iodata(values, color, binding, case, errors, 0, [?]|acc])], m, indent)
end
defp to_iodata({:brace, m, values}, color, binding, case, errors, indent, acc) do
indention([?{|to_iodata(values, color, binding, case, errors, 0, [?}|acc])], m, indent)
end
defp to_iodata({_, [], values}, color, binding, case, errors, indent, acc) do
to_iodata(values, color, binding, case, errors, indent, acc)
end
defp to_iodata({:ident, [_,_,{:tag, tag}|_]=m, [{:paren, _, _}]=values},color, binding, case, errors, indent, acc) do
indention(to_iodata(tag, color, binding, case, errors, indent, to_iodata(values, color, binding, case, errors, indent, acc)), m, indent)
end
defp to_iodata({tag, [{_, {l,c,_,_,_,_}}|_]=m, [{_, [{_, {l,lc,_,_,_,_}}|_], _}=left, {_, [{_, {l,rc,_,_,_,_}}|_], _}=right]}, color, binding, case, errors, indent, acc) when (c > lc and c < rc) do
to_iodata(left, color, binding, case, errors, indent, indention(to_iodata(tag, color, binding, case, errors, 0, to_iodata(right, color, binding, case, errors, 0, acc)), m, 0))
end
defp to_iodata({tag, [{_, {l,c,_,_,_,_}}|_]=m, [{_, [{_, {l,lc,_,_,_,_,_,_}}|_], _}=left, {_, [{_, {l,rc,_,_,_,_,_,_}}|_], _}=right]}, color, binding, case, errors, indent, acc) when (c > lc and c < rc) do
to_iodata(left, color, binding, case, errors, indent, indention(to_iodata(tag, color, binding, case, errors, 0, to_iodata(right, color, binding, case, errors, 0, acc)), m, 0))
end
defp to_iodata({tag, [{_, {l,c,_,_,_,_}}|_]=m, [{_, [{_, {l,lc,_,_,_,_}}|_], _}=left, {_, [{_, {l,rc,_,_,_,_,_,_}}|_], _}=right]}, color, binding, case, errors, indent, acc) when (c > lc and c < rc) do
to_iodata(left, color, binding, case, errors, indent, indention(to_iodata(tag, color, binding, case, errors, 0, to_iodata(right, color, binding, case, errors, 0, acc)), m, 0))
end
defp to_iodata({tag, [{_, {l,c,_,_,_,_}}|_]=m, [{_, [{_, {l,lc,_,_,_,_,_,_}}|_], _}=left, {_, [{_, {l,rc,_,_,_,_}}|_], _}=right]}, color, binding, case, errors, indent, acc) when (c > lc and c < rc) do
to_iodata(left, color, binding, case, errors, indent, indention(to_iodata(tag, color, binding, case, errors, 0, to_iodata(right, color, binding, case, errors, 0, acc)), m, 0))
end
defp to_iodata({tag, [{_, {l,c,_,_,_,_}}|_]=m, [{_, [{_, {ll,cc,_,_,_,_}}|_], _}]=values}, color, binding, case, errors, indent, acc) when (l == ll and c < cc and tag in ~w[desc asc not]a) do
to_iodata(values, color, binding, case, errors, indent, indention(to_iodata(tag, color, binding, case, errors, indent, acc), m, 0))
end
defp to_iodata({tag, [{_, {l,c,_,_,_,_}}|_]=m, [{_, [{_, {ll,cc,_,_,_,_,_,_}}|_], _}]=values}, color, binding, case, errors, indent, acc) when (l == ll and c < cc and tag in ~w[desc asc not]a) do
to_iodata(values, color, binding, case, errors, indent, indention(to_iodata(tag, color, binding, case, errors, indent, acc), m, 0))
end
defp to_iodata({tag, _m, [left, right]}, color, binding, case, errors, indent, acc) when tag in ~w[union except intersect]a do
to_iodata(left, color, binding, case, errors, indent, newline(to_iodata(tag, color, binding, case, errors, indent, to_iodata(right, color, binding, case, errors, indent, acc)), indent))
end
defp to_iodata({tag, m, [{_,_,_}|_]=values}, color, binding, case, errors, indent, acc) do
indention(to_iodata(tag, color, binding, case, errors, indent, to_iodata(values, color, binding, case, errors, indent, acc)), m, 0)
end
defp to_iodata({tag, m, value}=node, true, _binding, _case, errors, indent, acc) when tag in ~w[ident numeric special]a do
case node in errors do
true -> indention([@error, value, @reset|acc], m, indent)
false -> indention([@literal, value, @reset|acc], m, indent)
end
end
defp to_iodata({tag, m, value}=node, false, _binding, _case, errors, indent, acc) when tag in ~w[ident numeric special]a do
case node in errors do
true -> indention([value|acc], m, indent)
false -> indention([value|acc], m, indent)
end
end
defp to_iodata({:double_quote, m, value}=node, true, _binding, _case, errors, indent, acc) do
case node in errors do
true -> indention([?",@error,value, @reset, ?"|acc], m, indent)
false -> indention([?", @enclosed , value, @reset, ?"|acc], m, indent)
end
end
defp to_iodata({:double_quote, m, value}=node, false, _binding, _case, errors, indent, acc) do
case node in errors do
true -> indention([?",value, ?"|acc], m, indent)
false -> indention([?", value, ?"|acc], m, indent)
end
end
defp to_iodata([token|tokens], color, binding, case, errors, indent, acc) do
to_iodata(token, color, binding, case, errors, indent, to_iodata(tokens, color, binding, case, errors, indent, acc))
end
defp to_iodata([], _color, _binding, _case, _errors, _indent, acc) do
acc
end
end