Skip to content

Commit 2f02d5f

Browse files
committed
refactor sql.lock and validation
1 parent 2e90ba2 commit 2f02d5f

File tree

7 files changed

+42
-76
lines changed

7 files changed

+42
-76
lines changed

lib/formatter.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ defmodule SQL.MixFormatter do
1010

1111
@impl Mix.Tasks.Format
1212
def format(source, opts) do
13-
opts = Map.new(Keyword.merge([sql_lock: nil], opts))
13+
opts = Map.new(Keyword.merge([validate: nil], opts))
1414
{:ok, context, tokens} = SQL.Lexer.lex(source)
1515
{:ok, context, tokens} = SQL.Parser.parse(tokens, Map.merge(context, opts))
1616
iodata = SQL.Format.to_iodata(tokens, context)

lib/lexer.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ defmodule SQL.Lexer do
77
@zero "illegal zero width character"
88
@bidi "illegal bidi character"
99
@cgj "illegal cgj character"
10-
@context %{idx: 0, file: "nofile", binding: [], aliases: [], errors: [], module: nil, format: :static, case: :lower, sql_lock: nil}
10+
@context %{idx: 0, file: "nofile", binding: [], aliases: [], errors: [], module: nil, format: :static, case: :lower, validate: nil}
1111
def lex(binary, <<file::binary>> \\ "nofile", idx \\ 0) do
1212
case lex(binary, %{@context | file: file, idx: idx}, 0, 0, []) do
1313
{:error, error} -> raise TokenMissingError, [{:snippet, binary} | error]

lib/parser.ex

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -259,28 +259,27 @@ defmodule SQL.Parser do
259259
@order %{select: 0, from: 1, join: 2, where: 3, group: 4, having: 5, window: 6, order: 7, limit: 8, offset: 9, fetch: 10}
260260
defp sort(acc), do: Enum.sort_by(acc, fn {tag, _, _} -> Map.get(@order, tag) end, :asc)
261261

262-
defp validate(_, %{sql_lock: nil}, errors), do: errors
263-
defp validate({tag, _, _}, %{sql_lock: %{columns: []}}, errors) when tag in ~w[select having where on by order group]a, do: errors
264-
defp validate({tag, _, values}, %{sql_lock: %{columns: columns}}, errors) when tag in ~w[select having where on by order group]a do
265-
case validate_columns(columns, values, []) do
262+
defp validate(_, %{validate: nil}, errors), do: errors
263+
defp validate({tag, _, values}, %{validate: fun}, errors) when tag in ~w[select having where on by order group]a do
264+
case validate_columns(fun, values, []) do
266265
[] -> errors
267266
e -> e++errors
268267
end
269268
end
270-
defp validate({tag, _, _}, %{sql_lock: %{tables: []}}, errors) when tag in ~w[from join]a, do: errors
271-
defp validate({tag, _, values}, %{sql_lock: %{tables: tables}} = context, errors) when tag in ~w[from join]a do
269+
defp validate({tag, _, _}, %{validate: nil}, errors) when tag in ~w[from join]a, do: errors
270+
defp validate({tag, _, values}, %{validate: fun} = context, errors) when tag in ~w[from join]a do
272271
values
273272
|> Enum.reduce([], fn
274273
{:paren, _, _}, acc -> acc
275274
{:on, _, _} = node, acc -> validate(node, context, acc)
276-
{tag, _, _}=node, acc when tag in ~w[ident double_quote]a -> validate_table(tables, node, acc)
277-
{:as, _, [{tag, _, _}=node, _]}, acc when tag in ~w[ident double_quote]a -> validate_table(tables, node, acc)
278-
{:dot, _, [_, {tag, _, _}=node]}, acc when tag in ~w[ident double_quote]a -> validate_table(tables, node, acc)
279-
{:dot, _, [_, {:bracket, _, [{:ident, _, _}=node]}]}, acc -> validate_table(tables, node, acc)
280-
{:comma, _, [{:dot, _, [_, {:bracket, _, [{:ident, _, _}=node]}]}]}, acc -> validate_table(tables, node, acc)
281-
{:comma, _, [{:dot, _, [_, {tag, _, _}=node]}]}, acc when tag in ~w[ident double_quote]a -> validate_table(tables, node, acc)
282-
{:comma, _, [{:as, _, [{tag, _, _}=node, _]}]}, acc when tag in ~w[ident double_quote]a -> validate_table(tables, node, acc)
283-
{:comma, _, [{tag, _, _}=node]}, acc when tag in ~w[ident double_quote]a -> validate_table(tables, node, acc)
275+
{tag, _, _}=node, acc when tag in ~w[ident double_quote]a -> validate_table(fun, node, acc)
276+
{:as, _, [{tag, _, _}=node, _]}, acc when tag in ~w[ident double_quote]a -> validate_table(fun, node, acc)
277+
{:dot, _, [_, {tag, _, _}=node]}, acc when tag in ~w[ident double_quote]a -> validate_table(fun, node, acc)
278+
{:dot, _, [_, {:bracket, _, [{:ident, _, _}=node]}]}, acc -> validate_table(fun, node, acc)
279+
{:comma, _, [{:dot, _, [_, {:bracket, _, [{:ident, _, _}=node]}]}]}, acc -> validate_table(fun, node, acc)
280+
{:comma, _, [{:dot, _, [_, {tag, _, _}=node]}]}, acc when tag in ~w[ident double_quote]a -> validate_table(fun, node, acc)
281+
{:comma, _, [{:as, _, [{tag, _, _}=node, _]}]}, acc when tag in ~w[ident double_quote]a -> validate_table(fun, node, acc)
282+
{:comma, _, [{tag, _, _}=node]}, acc when tag in ~w[ident double_quote]a -> validate_table(fun, node, acc)
284283
end)
285284
|> case do
286285
[] -> errors
@@ -290,21 +289,21 @@ defmodule SQL.Parser do
290289
defp validate({tag, _, [{t, _, _}]}, _, errors) when tag in ~w[offset limit]a and t in ~w[numeric binary hexadecimal octal]a, do: errors
291290
defp validate({tag, _, _} = node, _, errors) when tag in ~w[offset limit]a, do: [node|errors]
292291

293-
defp validate_table(tables, {_, _, value}=node, acc) do
294-
case Enum.find(tables, false, fn %{table_name: {_, _, fun}} -> fun.(value) end) do
292+
defp validate_table(fun, {_, _, value}=node, acc) do
293+
case fun.(value) do
295294
true -> acc
296295
false -> [node|acc]
297296
end
298297
end
299298

300-
defp validate_columns(columns, {tag, _, value}=node, acc) when tag in ~w[ident double_quote]a do
301-
case Enum.find(columns, false, fn %{column_name: {_, _, fun}} -> fun.(value) end) do
299+
defp validate_columns(fun, {tag, _, value}=node, acc) when tag in ~w[ident double_quote]a do
300+
case fun.(value) do
302301
true -> acc
303302
false -> [node|acc]
304303
end
305304
end
306-
defp validate_columns(columns, [node|values], acc) do
307-
validate_columns(columns, values, validate_columns(columns, node, acc))
305+
defp validate_columns(fun, [node|values], acc) do
306+
validate_columns(fun, values, validate_columns(fun, node, acc))
308307
end
309-
defp validate_columns(_columns, _, acc), do: acc
308+
defp validate_columns(_fun, _, acc), do: acc
310309
end

lib/sql.ex

Lines changed: 19 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,12 @@ defmodule SQL do
1414
@doc false
1515
import SQL
1616
if File.exists?(Path.relative_to_cwd("sql.lock")) do
17-
config = Keyword.merge([case: :lower, adapter: Application.compile_env(:sql, :adapter, ANSI), lock: build_lock()], opts)
18-
Module.put_attribute(__MODULE__, :sql_config, Map.new(config))
19-
def sql_config, do: unquote(Macro.escape(Map.new(config)))
17+
Module.put_attribute(__MODULE__, :sql_config, %{case: opts[:case] || :lower, adapter: opts[:adapter] || Application.compile_env(:sql, :adapter, ANSI), validate: &__MODULE__.sql_validate/1})
18+
def sql_validate, do: unquote(build_lock())
2019
else
21-
IO.warn("Could not find a sql.lock, please run mix sql.get")
22-
config = Keyword.merge([case: :lower, adapter: Application.compile_env(:sql, :adapter, ANSI), lock: nil], opts)
23-
Module.put_attribute(__MODULE__, :sql_config, Map.new(config))
24-
def sql_config, do: unquote(Macro.escape(Map.new(config)))
20+
# IO.warn("Could not find a sql.lock, please run mix sql.get")
21+
Module.put_attribute(__MODULE__, :sql_config, %{case: opts[:case] || :lower, adapter: opts[:adapter] || Application.compile_env(:sql, :adapter, ANSI), validate: nil})
22+
def sql_validate, do: fn _ -> true end
2523
end
2624
end
2725
end
@@ -95,7 +93,7 @@ defmodule SQL do
9593

9694
@doc false
9795
def build(left, {:<<>>, _, _} = right, _modifiers, env) do
98-
config = %{case: :lower, adapter: Application.get_env(:sql, :adapter, ANSI), lock: nil}
96+
config = %{case: :lower, adapter: Application.get_env(:sql, :adapter, ANSI), validate: nil}
9997
config = if env.module, do: Module.get_attribute(env.module, :sql_config, config), else: config
10098
sql = struct(SQL, module: config.adapter)
10199
stack = if env.function do
@@ -107,7 +105,7 @@ defmodule SQL do
107105
{:static, data} ->
108106
id = id(data)
109107
{:ok, context, tokens} = SQL.Lexer.lex(data, env.file)
110-
{:ok, context, tokens} = SQL.Parser.parse(tokens, %{context|sql_lock: config.lock, module: config.adapter, case: config.case})
108+
{:ok, context, tokens} = SQL.Parser.parse(tokens, %{context|validate: config.validate, module: config.adapter, case: config.case})
111109
sql = %{sql | idx: context.idx, tokens: tokens, string: IO.iodata_to_binary(context.module.to_iodata(tokens, context)), inspect: __inspect__(tokens, context, stack), id: id}
112110
case context.binding do
113111
[] -> Macro.escape(sql)
@@ -126,7 +124,7 @@ defmodule SQL do
126124
end)
127125
{:ok, context, tokens} = tokens(right, file, idx, sql.id)
128126
tokens = t++tokens
129-
{string, inspect} = plan(tokens, %{context|sql_lock: config.lock, module: config.adapter, format: :dynamic}, sql.id, stack)
127+
{string, inspect} = plan(tokens, %{context|validate: config.validate, module: config.adapter, format: :dynamic}, sql.id, stack)
130128
%{sql | params: p++cast_params(context.binding, binding(), env, []), tokens: tokens, string: string, inspect: inspect}
131129
end
132130
end
@@ -215,46 +213,18 @@ defmodule SQL do
215213
@doc false
216214
def build_lock() do
217215
case elem(Code.eval_file("sql.lock", File.cwd!()), 0) do
218-
%{tables: tables, columns: columns} = data ->
219-
%{data |
220-
tables: Enum.map(tables, fn %{table_name: table_name} = table -> %{table | table_name: to_match(table_name)} end),
221-
columns: Enum.map(columns, fn %{table_name: table_name, column_name: column_name} = column -> %{column | table_name: to_match(table_name), column_name: to_match(column_name)} end)
222-
}
223-
data -> data
224-
end
225-
end
226-
227-
@doc false
228-
def to_match(value), do: {value, atom(value), function(value)}
229-
230-
@doc false
231-
def function(value) do
232-
{:fn, [], [head(value),{:->, [], [[{:_, [], Elixir}], false]}]}
233-
|> Code.eval_quoted()
234-
|> elem(0)
235-
end
236-
237-
@doc false
238-
def atom(value), do: String.to_atom(String.downcase(value))
239-
240-
@doc false
241-
def match(value), do: Enum.reduce(1..byte_size(value), [], fn n, acc -> [acc | [{:"b#{n}", [], Elixir}]] end)
242-
243-
@doc false
244-
def head(value), do: {:->, [], [[{:when, [],[match(value),guard(value)]}], true]}
245-
246-
@doc false
247-
def guard(value, acc \\ []) do
248-
{value, _n} = for <<k <- String.downcase(value)>>, reduce: {acc, 1} do
249-
{[], n} -> {__guard__(k, n), n+1}
250-
{acc, n} -> {{:and, [context: Elixir, imports: [{2, Kernel}]], [acc,__guard__(k, n)]}, n+1}
216+
%{tables: tables, columns: columns} ->
217+
tables = Enum.map(tables, fn %{table_name: value} -> String.downcase(value) end)
218+
columns = Enum.map(columns, fn %{table_name: table_name, column_name: column_name} -> {String.downcase(table_name), String.downcase(column_name)} end)
219+
quote bind_quoted: [tables: tables, columns: columns] do
220+
fn
221+
{table, column} -> {String.downcase(table), String.downcase(column)} in columns
222+
table -> String.downcase(table) in tables
223+
end
224+
end
225+
_data ->
226+
nil
251227
end
252-
value
253-
end
254-
255-
@doc false
256-
def __guard__(k, n) do
257-
{:in, [context: Elixir, imports: [{2, Kernel}]],[{:"b#{n}", [], Elixir},{:sigil_c, [delimiter: "\"", context: Elixir, imports: [{2, Kernel}]],[{:<<>>, [], ["#{<<k>>}#{String.upcase(<<k>>)}"]}, []]}]}
258228
end
259229

260230
@doc false

sql.lock

Lines changed: 0 additions & 1 deletion
This file was deleted.

test/parser_test.exs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,11 @@ defmodule SQL.ParserTest do
3131

3232
test "missing table with sql.lock and without tables in it" do
3333
{:ok, context, tokens} = SQL.Lexer.lex(@query)
34-
context = Map.put(context, :sql_lock, %{tables: [], columns: []})
3534
{:ok, %{errors: []}, _tokens} = SQL.Parser.parse(tokens, context)
3635
end
3736

3837
test "missing table without sql.lock" do
3938
{:ok, context, tokens} = SQL.Lexer.lex(@query)
40-
context = Map.put(context, :sql_lock, nil)
4139
{:ok, %{errors: []}, _tokens} = SQL.Parser.parse(tokens, context)
4240
end
4341
end

test/test_helper.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ end
77
defmodule SQLTest.Helpers do
88
def table_name([[[[[], b1], b2], b3],b4]) when b1 in ~c"tT" and b2 in ~c"eE" and b3 in ~c"sS" and b4 in ~c"tT", do: true
99
def table_name(_), do: false
10-
def set_sql_lock(context \\ %{}), do: Map.merge(context, %{sql_lock: %{tables: [%{table_name: {"test", :test, &table_name/1}}], columns: [%{column_name: {"test", :test, &table_name/1}}]}, module: SQL.Adapters.ANSI})
10+
def set_sql_lock(context \\ %{}), do: Map.merge(context, %{validate: &table_name/1, module: SQL.Adapters.ANSI})
1111
end
1212
ExUnit.start()

0 commit comments

Comments
 (0)