Skip to content

Commit 8dd2022

Browse files
CopilotSchultzer
andauthored
Add comprehensive test coverage for SQL module
Agent-Logs-Url: https://github.com/elixir-dbvisor/sql/sessions/c778cd35-8655-4f18-890b-15a9ce866fdd Co-authored-by: Schultzer <10013742+Schultzer@users.noreply.github.com>
1 parent bd316cd commit 8dd2022

14 files changed

Lines changed: 2570 additions & 0 deletions

test/adapter_comparison_test.exs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: 2025 DBVisor
3+
4+
defmodule SQL.AdapterComparisonTest do
5+
use ExUnit.Case, async: true
6+
7+
@adapters [
8+
SQL.Adapters.ANSI,
9+
SQL.Adapters.Postgres,
10+
SQL.Adapters.MySQL,
11+
SQL.Adapters.TDS
12+
]
13+
14+
describe "adapter consistency" do
15+
test "all adapters handle basic select" do
16+
for adapter <- @adapters do
17+
{:ok, context, tokens} = SQL.Lexer.lex("SELECT id, name FROM users")
18+
context = %{context | module: adapter, case: :lower}
19+
{:ok, context, tokens} = SQL.Parser.parse(tokens, context)
20+
result = IO.iodata_to_binary(adapter.to_iodata(tokens, context))
21+
assert String.contains?(result, "select")
22+
assert String.contains?(result, "from")
23+
end
24+
end
25+
26+
test "all adapters handle joins" do
27+
for adapter <- @adapters do
28+
{:ok, context, tokens} = SQL.Lexer.lex("SELECT * FROM users JOIN orders ON users.id = orders.user_id")
29+
context = %{context | module: adapter, case: :lower}
30+
{:ok, context, tokens} = SQL.Parser.parse(tokens, context)
31+
result = IO.iodata_to_binary(adapter.to_iodata(tokens, context))
32+
assert String.contains?(result, "join")
33+
assert String.contains?(result, "on")
34+
end
35+
end
36+
37+
test "all adapters handle WHERE clauses" do
38+
for adapter <- @adapters do
39+
{:ok, context, tokens} = SQL.Lexer.lex("SELECT * FROM users WHERE active = true AND age > 18")
40+
context = %{context | module: adapter, case: :lower}
41+
{:ok, context, tokens} = SQL.Parser.parse(tokens, context)
42+
result = IO.iodata_to_binary(adapter.to_iodata(tokens, context))
43+
assert String.contains?(result, "where")
44+
assert String.contains?(result, "and")
45+
end
46+
end
47+
48+
test "all adapters handle GROUP BY" do
49+
for adapter <- @adapters do
50+
{:ok, context, tokens} = SQL.Lexer.lex("SELECT department_id, COUNT(*) FROM users GROUP BY department_id")
51+
context = %{context | module: adapter, case: :lower}
52+
{:ok, context, tokens} = SQL.Parser.parse(tokens, context)
53+
result = IO.iodata_to_binary(adapter.to_iodata(tokens, context))
54+
assert String.contains?(result, "group by")
55+
end
56+
end
57+
58+
test "all adapters handle ORDER BY" do
59+
for adapter <- @adapters do
60+
{:ok, context, tokens} = SQL.Lexer.lex("SELECT * FROM users ORDER BY name ASC, created_at DESC")
61+
context = %{context | module: adapter, case: :lower}
62+
{:ok, context, tokens} = SQL.Parser.parse(tokens, context)
63+
result = IO.iodata_to_binary(adapter.to_iodata(tokens, context))
64+
assert String.contains?(result, "order by")
65+
assert String.contains?(result, "asc")
66+
assert String.contains?(result, "desc")
67+
end
68+
end
69+
70+
test "all adapters handle LIMIT" do
71+
for adapter <- @adapters do
72+
{:ok, context, tokens} = SQL.Lexer.lex("SELECT * FROM users LIMIT 10")
73+
context = %{context | module: adapter, case: :lower}
74+
{:ok, context, tokens} = SQL.Parser.parse(tokens, context)
75+
result = IO.iodata_to_binary(adapter.to_iodata(tokens, context))
76+
assert String.contains?(result, "limit")
77+
end
78+
end
79+
80+
test "all adapters handle subqueries" do
81+
for adapter <- @adapters do
82+
{:ok, context, tokens} = SQL.Lexer.lex("SELECT * FROM (SELECT id FROM users) AS subq")
83+
context = %{context | module: adapter, case: :lower}
84+
{:ok, context, tokens} = SQL.Parser.parse(tokens, context)
85+
result = IO.iodata_to_binary(adapter.to_iodata(tokens, context))
86+
assert String.contains?(result, "select")
87+
end
88+
end
89+
90+
test "all adapters handle UNION" do
91+
for adapter <- @adapters do
92+
{:ok, context, tokens} = SQL.Lexer.lex("(SELECT id FROM users) UNION (SELECT id FROM admins)")
93+
context = %{context | module: adapter, case: :lower}
94+
{:ok, context, tokens} = SQL.Parser.parse(tokens, context)
95+
result = IO.iodata_to_binary(adapter.to_iodata(tokens, context))
96+
assert String.contains?(result, "union")
97+
end
98+
end
99+
100+
test "all adapters handle CTEs" do
101+
for adapter <- @adapters do
102+
{:ok, context, tokens} = SQL.Lexer.lex("WITH active_users AS (SELECT * FROM users WHERE active = true) SELECT * FROM active_users")
103+
context = %{context | module: adapter, case: :lower}
104+
{:ok, context, tokens} = SQL.Parser.parse(tokens, context)
105+
result = IO.iodata_to_binary(adapter.to_iodata(tokens, context))
106+
assert String.contains?(result, "with")
107+
end
108+
end
109+
end
110+
111+
describe "parameter placeholders" do
112+
test "ANSI uses ? placeholder" do
113+
{:ok, context, tokens} = SQL.Lexer.lex("SELECT * FROM users WHERE id = {{id}}")
114+
context = %{context | module: SQL.Adapters.ANSI, case: :lower}
115+
{:ok, context, tokens} = SQL.Parser.parse(tokens, context)
116+
result = IO.iodata_to_binary(SQL.Adapters.ANSI.to_iodata(tokens, context))
117+
assert String.contains?(result, "?")
118+
end
119+
120+
test "MySQL uses ? placeholder" do
121+
{:ok, context, tokens} = SQL.Lexer.lex("SELECT * FROM users WHERE id = {{id}}")
122+
context = %{context | module: SQL.Adapters.MySQL, case: :lower}
123+
{:ok, context, tokens} = SQL.Parser.parse(tokens, context)
124+
result = IO.iodata_to_binary(SQL.Adapters.MySQL.to_iodata(tokens, context))
125+
assert String.contains?(result, "?")
126+
end
127+
128+
test "Postgres uses $N placeholder" do
129+
{:ok, context, tokens} = SQL.Lexer.lex("SELECT * FROM users WHERE id = {{id}}")
130+
context = %{context | module: SQL.Adapters.Postgres, case: :lower}
131+
{:ok, context, tokens} = SQL.Parser.parse(tokens, context)
132+
result = IO.iodata_to_binary(SQL.Adapters.Postgres.to_iodata(tokens, context))
133+
assert String.contains?(result, "$")
134+
end
135+
136+
test "TDS uses @N placeholder" do
137+
{:ok, context, tokens} = SQL.Lexer.lex("SELECT * FROM users WHERE id = {{id}}")
138+
context = %{context | module: SQL.Adapters.TDS, case: :lower}
139+
{:ok, context, tokens} = SQL.Parser.parse(tokens, context)
140+
result = IO.iodata_to_binary(SQL.Adapters.TDS.to_iodata(tokens, context))
141+
assert String.contains?(result, "@")
142+
end
143+
end
144+
end

test/array_json_test.exs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: 2025 DBVisor
3+
4+
defmodule SQL.ArrayJSONTest do
5+
use ExUnit.Case, async: true
6+
import SQL
7+
8+
describe "array operations" do
9+
test "array literal" do
10+
sql = ~SQL[select array[1, 2, 3]]
11+
assert String.contains?(to_string(sql), "array")
12+
end
13+
14+
test "array subscript" do
15+
sql = ~SQL[select tags[1]]
16+
assert String.contains?(to_string(sql), "[1]")
17+
end
18+
19+
test "array slice" do
20+
sql = ~SQL[select tags[1:3]]
21+
assert String.contains?(to_string(sql), "[1:3]")
22+
end
23+
24+
test "ANY with array" do
25+
sql = ~SQL[where id = any(array[1, 2, 3])]
26+
assert String.contains?(to_string(sql), "any")
27+
end
28+
29+
test "ALL with array" do
30+
sql = ~SQL[where id > all(array[1, 2, 3])]
31+
assert String.contains?(to_string(sql), "all")
32+
end
33+
34+
test "array_agg function" do
35+
sql = ~SQL[select array_agg(name)]
36+
assert String.contains?(to_string(sql), "array_agg")
37+
end
38+
39+
test "unnest function" do
40+
sql = ~SQL[select unnest(tags)]
41+
assert String.contains?(to_string(sql), "unnest")
42+
end
43+
44+
test "array_length function" do
45+
sql = ~SQL[select array_length(tags, 1)]
46+
assert String.contains?(to_string(sql), "array_length")
47+
end
48+
49+
test "array containment @>" do
50+
sql = ~SQL[where tags @> array['important']]
51+
assert String.contains?(to_string(sql), "@>")
52+
end
53+
54+
test "array contained by <@" do
55+
sql = ~SQL[where array['a', 'b'] <@ tags]
56+
assert String.contains?(to_string(sql), "<@")
57+
end
58+
59+
test "array overlap &&" do
60+
sql = ~SQL[where tags && array['a', 'b']]
61+
assert String.contains?(to_string(sql), "&&")
62+
end
63+
64+
test "array concatenation ||" do
65+
sql = ~SQL[select array[1, 2] || array[3, 4]]
66+
assert String.contains?(to_string(sql), "||")
67+
end
68+
end
69+
70+
describe "JSON operations" do
71+
test "JSON extract with ->" do
72+
sql = ~SQL[select data -> 'key']
73+
assert String.contains?(to_string(sql), "->")
74+
end
75+
76+
test "JSON extract text with ->>" do
77+
sql = ~SQL[select data ->> 'key']
78+
assert String.contains?(to_string(sql), "->>")
79+
end
80+
81+
test "JSON path with #>" do
82+
sql = ~SQL[select data #> '{a,b,c}']
83+
assert String.contains?(to_string(sql), "#>")
84+
end
85+
86+
test "JSON path text with #>>" do
87+
sql = ~SQL[select data #>> '{a,b,c}']
88+
assert String.contains?(to_string(sql), "#>>")
89+
end
90+
91+
test "JSON containment @>" do
92+
sql = ~SQL[where data @> '{"key": "value"}']
93+
assert String.contains?(to_string(sql), "@>")
94+
end
95+
96+
test "JSON contained <@" do
97+
sql = ~SQL[where '{"key": "value"}' <@ data]
98+
assert String.contains?(to_string(sql), "<@")
99+
end
100+
101+
test "JSON key exists ?" do
102+
sql = ~SQL[where data ? 'key']
103+
assert String.contains?(to_string(sql), "?")
104+
end
105+
106+
test "json_build_object function" do
107+
sql = ~SQL[select json_build_object('name', name, 'age', age)]
108+
assert String.contains?(to_string(sql), "json_build_object")
109+
end
110+
111+
test "jsonb_build_object function" do
112+
sql = ~SQL[select jsonb_build_object('name', name, 'age', age)]
113+
assert String.contains?(to_string(sql), "jsonb_build_object")
114+
end
115+
116+
test "json_agg function" do
117+
sql = ~SQL[select json_agg(row_to_json(t))]
118+
assert String.contains?(to_string(sql), "json_agg")
119+
end
120+
121+
test "jsonb_array_elements function" do
122+
sql = ~SQL[select jsonb_array_elements(data)]
123+
assert String.contains?(to_string(sql), "jsonb_array_elements")
124+
end
125+
126+
test "jsonb_each function" do
127+
sql = ~SQL[select * from jsonb_each(data)]
128+
assert String.contains?(to_string(sql), "jsonb_each")
129+
end
130+
131+
test "jsonb_set function" do
132+
sql = ~SQL[select jsonb_set(data, '{key}', '"value"')]
133+
assert String.contains?(to_string(sql), "jsonb_set")
134+
end
135+
136+
test "jsonb_strip_nulls function" do
137+
sql = ~SQL[select jsonb_strip_nulls(data)]
138+
assert String.contains?(to_string(sql), "jsonb_strip_nulls")
139+
end
140+
end
141+
142+
describe "JSONB concatenation and deletion" do
143+
test "JSONB concatenation ||" do
144+
sql = ~SQL[select '{"a": 1}'::jsonb || '{"b": 2}'::jsonb]
145+
assert String.contains?(to_string(sql), "||")
146+
end
147+
148+
test "JSONB key deletion -" do
149+
sql = ~SQL[select data - 'key']
150+
assert String.contains?(to_string(sql), "-")
151+
end
152+
153+
test "JSONB path deletion #-" do
154+
sql = ~SQL[select data #- '{a,b}']
155+
assert String.contains?(to_string(sql), "#-")
156+
end
157+
end
158+
end

0 commit comments

Comments
 (0)