Skip to content

Commit 911a079

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

File tree

12 files changed

+100
-86
lines changed

12 files changed

+100
-86
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
jobs:
55
test:
66
runs-on: ubuntu-22.04
7-
name: OTP 27 / Elixir 1.18
7+
name: OTP 27 / Elixir 1.19
88
services:
99
postgres:
1010
image: postgres:latest
@@ -17,7 +17,7 @@ jobs:
1717
- uses: erlef/setup-beam@v1
1818
with:
1919
otp-version: 27
20-
elixir-version: 1.18
20+
elixir-version: main
2121
- uses: actions/cache@v4
2222
with:
2323
path: |

.github/workflows/conformance.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
jobs:
55
test:
66
runs-on: ubuntu-22.04
7-
name: OTP 27 / Elixir 1.18
7+
name: OTP 28 / Elixir main
88
services:
99
postgres:
1010
image: postgres:latest
@@ -22,8 +22,8 @@ jobs:
2222
repository: elliotchance/sqltest
2323
- uses: erlef/setup-beam@v1
2424
with:
25-
otp-version: 27
26-
elixir-version: 1.18
25+
otp-version: 28
26+
elixir-version: main
2727
- uses: actions/cache@v4
2828
with:
2929
path: |

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,35 @@ iex(6)> inspect(sql)
6161
[%User{id: 1, email: "john@example.com"}, %User{id: 2, email: "jane@example.com"}]
6262
```
6363

64+
## Compile time errors
65+
run `mix sql.get` to generate your `sql.lock` file which is used for error reporting.
66+
67+
```elixir
68+
==> myapp
69+
Compiling 1 file (.ex)
70+
warning:
71+
the relation OPS does not exist
72+
the relation email is mentioned 2 times but does not exist
73+
the relation users does not exist
74+
~SQL"""
75+
select
76+
email,
77+
1 + "OPS"
78+
from
79+
users
80+
where
81+
email = 'john@example.com'
82+
"""
83+
lib/myapp.ex:18: Myapp.list_users/0
84+
(sql 0.4.0) lib/sql.ex:225: SQL.__inspect__/3
85+
(sql 0.4.0) lib/sql.ex:115: SQL.build/4
86+
(elixir 1.20.0-dev) src/elixir_dispatch.erl:263: :elixir_dispatch.expand_macro_fun/7
87+
(elixir 1.20.0-dev) src/elixir_dispatch.erl:122: :elixir_dispatch.dispatch_import/6
88+
(elixir 1.20.0-dev) src/elixir_clauses.erl:192: :elixir_clauses.def/3
89+
(elixir 1.20.0-dev) src/elixir_def.erl:218: :elixir_def."-store_definition/10-lc$^0/1-0-"/3
90+
(elixir 1.20.0-dev) src/elixir_def.erl:219: :elixir_def.store_definition/10
91+
```
92+
6493
## Benchmark
6594
You can find benchmark results [here](https://github.com/elixir-dbvisor/sql/benchmarks) or run `mix sql.bench`
6695

lib/format.ex

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ defmodule SQL.Format do
99

1010
@doc false
1111
@doc since: "0.4.0"
12-
def to_iodata(tokens, context), do: newline(to_iodata(tokens, context.binding, context.case, context.errors, 0, []), 0)
12+
def to_iodata(tokens, context, indent \\ 0), do: newline(to_iodata(tokens, context.binding, context.case, context.errors, indent, []), indent)
1313

1414
defp indention(acc, [{:preset, {_,0}},_,{:offset, {_,0,_,_}}|_], 0), do: acc
1515
defp indention(acc, [_,{:offset, {_,0}}|_], 0), do: acc
@@ -27,18 +27,18 @@ defmodule SQL.Format do
2727
{reserved, non_reserved, operators} = SQL.BNF.get_rules()
2828
for atom <- Enum.uniq(Enum.map(reserved++non_reserved++operators,&elem(&1, 0))), atom not in newline do
2929
defp to_iodata(unquote(atom), _binding ,:lower, _errors, _indent, acc) do
30-
[unquote("#{atom}")|acc]
30+
["\e[35m", unquote("#{atom}"), "\e[0m"|acc]
3131
end
3232
defp to_iodata(unquote(atom), _binding, :upper, _errors, _indent, acc) do
33-
[unquote(String.upcase("#{atom}"))|acc]
33+
["\e[35m", unquote(String.upcase("#{atom}")), "\e[0m"|acc]
3434
end
3535
end
3636
for atom <- newline do
3737
defp to_iodata({unquote(atom), m, values}, binding, :lower=case, errors, indent, acc) do
38-
newline(indention([unquote("#{atom}")|newline(to_iodata(values, binding, case, errors, indent+1, acc), indent+1)], m, indent), indent)
38+
newline(indention(["\e[35m", unquote("#{atom}"), "\e[0m"|newline(to_iodata(values, binding, case, errors, indent+1, acc), indent+1)], m, indent), indent)
3939
end
4040
defp to_iodata({unquote(atom), m, values}, binding, :upper=case, errors, indent, acc) do
41-
newline(indention([unquote(String.upcase("#{atom}"))|newline(to_iodata(values, binding, case, errors, indent+1, acc), indent+1)], m, indent), indent)
41+
newline(indention(["\e[35m", unquote(String.upcase("#{atom}")), "\e[0m"|newline(to_iodata(values, binding, case, errors, indent+1, acc), indent+1)], m, indent), indent)
4242
end
4343
end
4444
defp to_iodata(:comma, _binding, _case, _errors, 0, acc) do
@@ -66,10 +66,10 @@ defmodule SQL.Format do
6666
[?\\,?*,value,?*,?\\|acc]
6767
end
6868
defp to_iodata({:quote, m, value}, _binding, _case, _errors, indent, acc) do
69-
indention([?',value,?'|acc], m, indent)
69+
indention([?',"\e[32m" , value, "\e[0m",?'|acc], m, indent)
7070
end
7171
defp to_iodata({:backtick, m, value}, _binding, _case, _errors, indent, acc) do
72-
indention([?`,value,?`|acc], m, indent)
72+
indention([?`,"\e[32m" , value, "\e[0m",?`|acc], m, indent)
7373
end
7474
defp to_iodata({tag, m, []}, binding, case, errors, indent, acc) do
7575
indention(to_iodata(tag, binding, case, errors, indent, acc), m, indent)
@@ -101,13 +101,13 @@ defmodule SQL.Format do
101101
defp to_iodata({tag, m, value}=node, _binding, _case, errors, indent, acc) when tag in ~w[ident numeric special]a do
102102
case node in errors do
103103
true -> indention(["\e[31m", value, "\e[0m"|acc], m, indent)
104-
false -> indention([value|acc], m, indent)
104+
false -> indention(["\e[33m", value, "\e[0m"|acc], m, indent)
105105
end
106106
end
107107
defp to_iodata({:double_quote, m, value}=node, _binding, _case, errors, indent, acc) do
108108
case node in errors do
109109
true -> indention([?","\e[31m",value, "\e[0m", ?"|acc], m, indent)
110-
false -> indention([?", value, ?"|acc], m, indent)
110+
false -> indention([?", "\e[32m" , value, "\e[0m", ?"|acc], m, indent)
111111
end
112112
end
113113
defp to_iodata({tag, m, values}, binding, case, errors, indent, acc) do

lib/mix/tasks/sql.get.ex

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,49 +7,43 @@ defmodule Mix.Tasks.Sql.Get do
77
import Mix.Generator
88
@moduledoc since: "0.3.0"
99

10-
defguard is_postgres(value) when value in [Ecto.Adapters.Postgres, Postgrex]
11-
defguard is_mysql(value) when value in [Ecto.Adapters.MyXQL, MyXQL]
12-
defguard is_tds(value) when value in [Ecto.Adapters.Tds, Tds]
13-
defguard is_sqlite(value) when value in [Ecto.Adapters.SQLite3, Exqlite]
14-
1510
@shortdoc "Generates a sql.lock"
1611
def run(args) do
1712
app = Mix.Project.config()[:app]
1813
Application.load(app)
1914
repos = Application.get_env(app, :ecto_repos)
2015
Mix.Task.run("app.config", args)
2116
Application.ensure_all_started(:ecto_sql, :permanent)
22-
lock = Enum.reduce(repos, %{}, fn repo, acc ->
17+
lock = Enum.reduce(repos, [], fn repo, acc ->
2318
repo.__adapter__().ensure_all_started(repo.config(), [])
2419
repo.start_link(repo.config())
25-
Map.merge(acc, to_lock(repo, repo.__adapter__()), fn _, l, r -> Enum.uniq(:lists.flatten(l, r)) end)
20+
get(repo)++acc
2621
end)
2722
create_file("sql.lock", lock_template(lock: lock), force: true)
2823
end
2924

30-
def get(repo, sql) do
25+
defp get(repo) do
26+
sql = to_query(repo.__adapter__())
3127
{:ok, %{columns: columns, rows: rows}} = repo.query(to_string(sql), [])
3228
columns = Enum.map(columns, &String.to_atom(String.downcase(&1)))
3329
Enum.map(rows, &Map.new(Enum.zip(columns, &1)))
3430
end
3531

32+
defp to_query(value) when value in [Ecto.Adapters.Postgres, Postgrex], do: ~SQL"select * from information_schema.columns where table_schema not in ('information_schema', 'pg_catalog')"
33+
defp to_query(value) when value in [Ecto.Adapters.Tds, Tds], do: ~SQL"select * from information_schema.columns where table_schema not in ('information_schema', 'pg_catalog')"
34+
defp to_query(value) when value in [Ecto.Adapters.MyXQL, MyXQL], do: ~SQL"select * from information_schema.columns where table_schema not in ('mysql', 'performance_schema', 'sys')"
35+
defp to_query(value) when value in [Ecto.Adapters.SQLite3, Exqlite], do: ~SQL"select * from sqlite_master join pragma_table_info (sqlite_master.name)"
36+
3637
embed_template(:lock, """
37-
<%= inspect @lock, pretty: true, limit: :infinity %>
38+
%{
39+
columns: <%= inspect @lock, pretty: true, limit: :infinity %>,
40+
validate: fn
41+
<%= for %{table_name: table, column_name: column} <- @lock do %>
42+
<%= inspect String.to_charlist(table) %>, nil -> true
43+
<%= inspect String.to_charlist(table) %>, <%= inspect String.to_charlist(column) %> -> true
44+
<% end %>
45+
_, _ -> false
46+
end
47+
}
3848
""")
39-
40-
def to_lock(repo, value) when not is_sqlite(value) do
41-
%{columns: get(repo, to_query(value, :columns)), tables: get(repo, to_query(value, :tables))}
42-
end
43-
def to_lock(repo, value) when is_sqlite(value) do
44-
tables = get(repo, to_query(value, :tables))
45-
%{columns: tables, tables: tables}
46-
end
47-
48-
def to_query(value, :tables) when is_postgres(value), do: ~SQL"select * from information_schema.tables where table_schema not in ('information_schema', 'pg_catalog')"
49-
def to_query(value, :columns) when is_postgres(value), do: ~SQL"select * from information_schema.columns where table_schema not in ('information_schema', 'pg_catalog')"
50-
def to_query(value, :tables) when is_tds(value), do: ~SQL"select * from information_schema.tables where table_schema not in ('information_schema', 'pg_catalog')"
51-
def to_query(value, :columns) when is_tds(value), do: ~SQL"select * from information_schema.columns where table_schema not in ('information_schema', 'pg_catalog')"
52-
def to_query(value, :tables) when is_mysql(value), do: ~SQL"select * from information_schema.tables where table_schema not in ('mysql', 'performance_schema', 'sys')"
53-
def to_query(value, :columns) when is_mysql(value), do: ~SQL"select * from information_schema.columns where table_schema not in ('mysql', 'performance_schema', 'sys')"
54-
def to_query(value, :tables) when is_sqlite(value), do: ~SQL"select * from sqlite_master join pragma_table_info (sqlite_master.name)"
5549
end

lib/parser.ex

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -266,11 +266,9 @@ defmodule SQL.Parser do
266266
e -> e++errors
267267
end
268268
end
269-
defp validate({tag, _, _}, %{validate: nil}, errors) when tag in ~w[from join]a, do: errors
270269
defp validate({tag, _, values}, %{validate: fun} = context, errors) when tag in ~w[from join]a do
271270
values
272271
|> Enum.reduce([], fn
273-
{:paren, _, _}, acc -> acc
274272
{:on, _, _} = node, acc -> validate(node, context, acc)
275273
{tag, _, _}=node, acc when tag in ~w[ident double_quote]a -> validate_table(fun, node, acc)
276274
{:as, _, [{tag, _, _}=node, _]}, acc when tag in ~w[ident double_quote]a -> validate_table(fun, node, acc)
@@ -279,7 +277,8 @@ defmodule SQL.Parser do
279277
{:comma, _, [{:dot, _, [_, {:bracket, _, [{:ident, _, _}=node]}]}]}, acc -> validate_table(fun, node, acc)
280278
{:comma, _, [{:dot, _, [_, {tag, _, _}=node]}]}, acc when tag in ~w[ident double_quote]a -> validate_table(fun, node, acc)
281279
{: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)
280+
{:comma, _, [{tag, _, _}=node|_]}, acc when tag in ~w[ident double_quote]a -> validate_table(fun, node, acc)
281+
_, acc -> acc
283282
end)
284283
|> case do
285284
[] -> errors
@@ -290,18 +289,19 @@ defmodule SQL.Parser do
290289
defp validate({tag, _, _} = node, _, errors) when tag in ~w[offset limit]a, do: [node|errors]
291290

292291
defp validate_table(fun, {_, _, value}=node, acc) do
293-
case fun.(value) do
292+
case fun.(value, nil) do
294293
true -> acc
295294
false -> [node|acc]
296295
end
297296
end
298297

299298
defp validate_columns(fun, {tag, _, value}=node, acc) when tag in ~w[ident double_quote]a do
300-
case fun.(value) do
299+
case fun.(nil, value) do
301300
true -> acc
302301
false -> [node|acc]
303302
end
304303
end
304+
defp validate_columns(fun, {_, _, values}, acc), do: validate_columns(fun, values, acc)
305305
defp validate_columns(fun, [node|values], acc) do
306306
validate_columns(fun, values, validate_columns(fun, node, acc))
307307
end

lib/sql.ex

Lines changed: 25 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,16 @@ defmodule SQL do
1313
quote bind_quoted: [opts: opts] do
1414
@doc false
1515
import SQL
16-
if File.exists?(Path.relative_to_cwd("sql.lock")) do
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())
19-
else
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
23-
end
16+
config = Map.new(Keyword.merge([case: :lower, adapter: Application.compile_env(:sql, :adapter, ANSI), validate: fn _, _ -> true end], opts))
17+
@external_resource Path.relative_to_cwd("sql.lock")
18+
config = with true <- File.exists?(Path.relative_to_cwd("sql.lock")),
19+
%{validate: validate} <- elem(Code.eval_file("sql.lock", File.cwd!()), 0) do
20+
%{config | validate: validate}
21+
else
22+
_ -> config
23+
end
24+
Module.put_attribute(__MODULE__, :sql_config, config)
25+
def sql_repo, do: unquote(opts[:repo] || config.adapter)
2426
end
2527
end
2628

@@ -93,9 +95,9 @@ defmodule SQL do
9395

9496
@doc false
9597
def build(left, {:<<>>, _, _} = right, _modifiers, env) do
96-
config = %{case: :lower, adapter: Application.get_env(:sql, :adapter, ANSI), validate: nil}
98+
config = %{case: :lower, adapter: Application.get_env(:sql, :adapter, ANSI), validate: fn _, _ -> true end}
9799
config = if env.module, do: Module.get_attribute(env.module, :sql_config, config), else: config
98-
sql = struct(SQL, module: config.adapter)
100+
sql = struct(SQL, module: env.module)
99101
stack = if env.function do
100102
{env.module, elem(env.function, 0), elem(env.function, 1), [file: Path.relative_to_cwd(env.file), line: env.line]}
101103
else
@@ -105,6 +107,7 @@ defmodule SQL do
105107
{:static, data} ->
106108
id = id(data)
107109
{:ok, context, tokens} = SQL.Lexer.lex(data, env.file)
110+
%{context|validate: config.validate, module: config.adapter, case: config.case}
108111
{:ok, context, tokens} = SQL.Parser.parse(tokens, %{context|validate: config.validate, module: config.adapter, case: config.case})
109112
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}
110113
case context.binding do
@@ -117,7 +120,7 @@ defmodule SQL do
117120

118121
{:dynamic, data} ->
119122
sql = %{sql | id: id(data)}
120-
quote bind_quoted: [left: Macro.unpipe(left), right: right, file: env.file, data: data, sql: Macro.escape(sql), env: Macro.escape(env), config: Macro.escape(config), stack: Macro.escape(stack)] do
123+
quote bind_quoted: [left: Macro.unpipe(left), right: right, file: env.file, data: data, sql: Macro.escape(sql), env: Macro.escape(env), config: Macro.escape(%{config| validate: nil}), stack: Macro.escape(stack)] do
121124
{t,p,idx} = Enum.reduce(left, {[], [], 0}, fn
122125
{[], 0}, acc -> acc
123126
{v, 0}, {t, p, idx} -> {t++v.tokens, p++v.params, idx+v.idx}
@@ -210,26 +213,9 @@ defmodule SQL do
210213
end
211214
end
212215

213-
@doc false
214-
def build_lock() do
215-
case elem(Code.eval_file("sql.lock", File.cwd!()), 0) do
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
227-
end
228-
end
229-
230216
@doc false
231217
def __inspect__(tokens, context, stack) do
232-
inspect = IO.iodata_to_binary(["~SQL\"\"\""|[SQL.Format.to_iodata(tokens, context)|~c"\n\"\"\""]])
218+
inspect = IO.iodata_to_binary(["\e[0m", " ~SQL\"\"\""|[SQL.Format.to_iodata(tokens, context, 1)|~c"\n \"\"\""]])
233219
case context.errors do
234220
[] -> inspect
235221
errors ->
@@ -241,10 +227,10 @@ defmodule SQL do
241227

242228
@doc false
243229
def format_error(errors), do: Enum.group_by(errors, &elem(&1, 2)) |> Enum.reduce([], fn
244-
{k, [{:special, _, _}]}, acc -> [acc|["the operator \e[31m",k,"\e[0m is invalid, did you mean any of #{__suggest__(k)}\n"]]
245-
{k, [{:special, _, _}|_]=v}, acc -> [acc|["the operator \e[31m",k,"\e[0m is mentioned #{length(v)} times but is invalid, did you mean any of #{__suggest__(k)}\n"]]
246-
{k, [_]}, acc -> [acc|["the relation \e[31m",k,"\e[0m does not exist\n"]]
247-
{k, v}, acc -> [acc|["the relation \e[31m",k,"\e[0m is mentioned #{length(v)} times but does not exist\n"]]
230+
{k, [{:special, _, _}]}, acc -> [acc|[" the operator \e[31m",k,"\e[0m is invalid, did you mean any of #{__suggest__(k)}\n"]]
231+
{k, [{:special, _, _}|_]=v}, acc -> [acc|[" the operator \e[31m",k,"\e[0m is mentioned #{length(v)} times but is invalid, did you mean any of #{__suggest__(k)}\n"]]
232+
{k, [_]}, acc -> [acc|[" the relation \e[31m",k,"\e[0m does not exist\n"]]
233+
{k, v}, acc -> [acc|[" the relation \e[31m",k,"\e[0m is mentioned #{length(v)} times but does not exist\n"]]
248234
end)
249235

250236
@doc false
@@ -257,8 +243,13 @@ defmodule SQL do
257243
def member?(_enumerable, _element) do
258244
{:error, __MODULE__}
259245
end
246+
def reduce(%SQL{fn: nil} = enumerable, acc, fun) do
247+
repo = enumerable.module.sql_repo()
248+
%{rows: rows, columns: columns} = repo.query!(enumerable.string, enumerable.params)
249+
Enumerable.reduce(rows, acc, fn row, acc -> fun.(Map.new(Enum.zip(columns, row)), acc) end)
250+
end
260251
def reduce(%SQL{} = enumerable, acc, fun) do
261-
repo = enumerable.module.sql_config()[:repo]
252+
repo = enumerable.module.sql_repo()
262253
%{rows: rows, columns: columns} = repo.query!(enumerable.string, enumerable.params)
263254
fun = case Function.info(enumerable.fn, :arity) do
264255
{:arity, 1} -> fn row, acc -> fun.(enumerable.fn.(row), acc) end

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ defmodule SQL.MixProject do
1010
[
1111
app: :sql,
1212
version: @version,
13-
elixir: "~> 1.16",
13+
elixir: "~> 1.19",
1414
deps: deps(),
1515
description: "Brings an extensible SQL parser and sigil to Elixir, confidently write SQL with automatic parameterized queries.",
1616
name: "SQL",

test/formatter_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ defmodule SQL.FormatterTest do
99
end
1010

1111
test "format/2 preserve interpolation" do
12-
assert "\nwith recursive temp(n, fact) as (\n select\n 0,\n 1\n union all\n select\n n + {{one}},\n (n + {{one}}) * fact\n from\n temp\n where\n n < 9\n)" == SQL.MixFormatter.format("with recursive temp(n, fact) as (select 0, 1 union all select n + {{one}}, (n + {{one}}) * fact from temp where n < 9)", [])
12+
assert "\n\e[35mwith\e[0m \e[35mrecursive\e[0m \e[33mtemp\e[0m(\e[33mn\e[0m, \e[33mfact\e[0m) \e[35mas\e[0m (\n \e[35mselect\e[0m\n \e[33m0\e[0m,\n \e[33m1\e[0m\n \e[35munion\e[0m \e[35mall\e[0m\n \e[35mselect\e[0m\n \e[33mn\e[0m \e[35m+\e[0m {{one}},\n (\e[33mn\e[0m \e[35m+\e[0m {{one}}) \e[35m*\e[0m \e[33mfact\e[0m\n \e[35mfrom\e[0m\n \e[33mtemp\e[0m\n \e[35mwhere\e[0m\n \e[33mn\e[0m \e[35m<\e[0m \e[33m9\e[0m\n)" == SQL.MixFormatter.format("with recursive temp(n, fact) as (select 0, 1 union all select n + {{one}}, (n + {{one}}) * fact from temp where n < 9)", [])
1313
end
1414
end

test/parser_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ defmodule SQL.ParserTest do
2525
describe "error" do
2626
test "missing table with sql.lock and with tables in it" do
2727
{:ok, context, tokens} = SQL.Lexer.lex(@query)
28-
{:ok, %{errors: errors}, _tokens} = SQL.Parser.parse(tokens, SQLTest.Helpers.set_sql_lock(context))
29-
assert length(errors) == 20
28+
{:ok, %{errors: errors}, _tokens} = SQL.Parser.parse(tokens, SQLTest.Helpers.set_validate(context))
29+
assert length(errors) == 30
3030
end
3131

3232
test "missing table with sql.lock and without tables in it" do

0 commit comments

Comments
 (0)