Skip to content

Commit 9c712a0

Browse files
committed
Add conformance tests
1 parent 905b120 commit 9c712a0

File tree

5 files changed

+108
-2
lines changed

5 files changed

+108
-2
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
on: push
22
jobs:
33
test:
4-
runs-on: ubuntu-latest
4+
runs-on: ubuntu-22.04
55
name: OTP 27 / Elixir 1.18
66
services:
77
postgres:

.github/workflows/conformance.yml

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
on: push
2+
jobs:
3+
test:
4+
runs-on: ubuntu-22.04
5+
name: OTP 27 / Elixir 1.18
6+
services:
7+
postgres:
8+
image: postgres:latest
9+
env:
10+
POSTGRES_PASSWORD: postgres
11+
ports: ["5432:5432"]
12+
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
13+
steps:
14+
- name: Checkout ${{github.repository}}
15+
uses: actions/checkout@v4
16+
- name: Checkout sqltest
17+
uses: actions/checkout@v4
18+
with:
19+
path: sqltest
20+
repository: elliotchance/sqltest
21+
- uses: erlef/setup-beam@v1
22+
with:
23+
otp-version: 27
24+
elixir-version: 1.18
25+
- uses: actions/cache@v4
26+
with:
27+
path: |
28+
deps
29+
_build
30+
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
31+
restore-keys: |
32+
${{ runner.os }}-mix-
33+
- run: mix deps.get && mix sql.gen.test sqltest/standards/2016 && mix test

lib/mix/tasks/sql.gen.test.ex

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: 2025 DBVisor
3+
4+
defmodule Mix.Tasks.Sql.Gen.Test do
5+
use Mix.Task
6+
import Mix.Generator
7+
8+
@shortdoc "Generates test from the BNF rules"
9+
@shortdoc since: "0.2.0"
10+
11+
def run([base]) do
12+
create_file("test/conformance/e_test.exs", test_template([mod: SQL.Conformance.ETest, dir: Path.join(base, "E")]))
13+
create_file("test/conformance/f_test.exs", test_template([mod: SQL.Conformance.FTest, dir: Path.join(base, "F")]))
14+
create_file("test/conformance/s_test.exs", test_template([mod: SQL.Conformance.STest, dir: Path.join(base, "S")]))
15+
create_file("test/conformance/t_test.exs", test_template([mod: SQL.Conformance.TTest, dir: Path.join(base, "T")]))
16+
end
17+
18+
def generate_test(dir) do
19+
for path <- File.ls!(dir), path =~ ".tests.yml", [{~c"feature", feature}, {~c"id", id}, {~c"sql", sql}] <- :yamerl.decode_file(to_charlist(Path.join(dir, path))) do
20+
statements = if is_list(hd(sql)), do: sql, else: [sql]
21+
statements = Enum.map(statements, &String.replace(to_string(&1), ~r{(VARING)}, "VARYING"))
22+
{"#{feature} #{id}", Enum.map(statements, &{trim(&1), &1})}
23+
end
24+
end
25+
26+
def trim(value) do
27+
value
28+
|> String.replace(~r{\(\s+\b}, &String.replace(&1, " ", ""))
29+
|> String.replace(~r{\(\s+'}, &String.replace(&1, " ", ""))
30+
|> String.replace(~r{\(\s+"}, &String.replace(&1, " ", ""))
31+
|> String.replace(~r{\(\s+\*}, &String.replace(&1, " ", ""))
32+
|> String.replace(~r{[[:alpha:]]+\s+\(}, &String.replace(&1, " ", ""))
33+
|> String.replace(~r{\b\s+\,}, &String.replace(&1, " ", ""))
34+
|> String.replace(~r{\)\s+\,}, &String.replace(&1, " ", ""))
35+
|> String.replace(~r{\'\s+\,}, &String.replace(&1, " ", ""))
36+
|> String.replace(~r{\b\s+\)}, &String.replace(&1, " ", ""))
37+
|> String.replace(~r{'\s+\)}, &String.replace(&1, " ", ""))
38+
|> String.replace(~r{\*\s+\)}, &String.replace(&1, " ", ""))
39+
|> String.replace(~r{\)\s+\)}, &String.replace(&1, " ", ""))
40+
|> String.replace(~r{\W(SELECT|REFERENCES|INSERT|UPDATE|IN|MYTEMP)\(}, &Enum.join(Regex.split(~r{\(}, &1, include_captures: true, trim: true), " "))
41+
|> String.replace(~r{^(SELECT)\(}, &Enum.join(Regex.split(~r{\(}, &1, include_captures: true, trim: true), " "))
42+
|> String.replace(~r{\s+\.\s+}, &String.replace(&1, " ", ""))
43+
|> String.replace(~r{\d\s(\+|\-)\d}, &Enum.join(Enum.map(Regex.split(~r{\+|\-}, &1, include_captures: true, trim: true), fn x -> String.trim(x) end), " "))
44+
|> String.trim()
45+
end
46+
47+
embed_template(:test, """
48+
# SPDX-License-Identifier: Apache-2.0
49+
# SPDX-FileCopyrightText: 2025 DBVisor
50+
51+
defmodule <%= inspect @mod %>.Adapter do
52+
use SQL.Token
53+
54+
def token_to_string(value, mod \\\\ __MODULE__)
55+
def token_to_string(value, _mod) when is_atom(value), do: String.upcase(Atom.to_string(value))
56+
def token_to_string(token, mod), do: SQL.Adapters.ANSI.token_to_string(token, mod)
57+
end
58+
defmodule <%= inspect @mod %> do
59+
use ExUnit.Case, async: true
60+
use SQL, adapter: <%= inspect @mod %>.Adapter
61+
62+
<%= for {name, statements} <- generate_test(@dir) do %>
63+
test <%= inspect name %> do
64+
<%= for {left, right} <- statements do %>
65+
assert ~s{<%= left %>} == to_string(~SQL[<%= right %>])
66+
<% end %>
67+
end
68+
<% end %>
69+
end
70+
""")
71+
end

mix.exs

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ defmodule SQL.MixProject do
1616
name: "SQL",
1717
docs: docs(),
1818
package: package(),
19-
aliases: [bench: "run bench.exs"]
19+
aliases: ["sql.bench": "run bench.exs"]
2020
]
2121
end
2222

@@ -44,6 +44,7 @@ defmodule SQL.MixProject do
4444
{:ecto_sql, "~> 3.12", only: [:dev, :test]},
4545
{:ex_doc, "~> 0.37", only: :dev},
4646
{:postgrex, ">= 0.0.0", only: [:dev, :test]},
47+
{:yamerl, ">= 0.0.0", only: [:dev, :test]},
4748
]
4849
end
4950
end

mix.lock

+1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@
1414
"postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"},
1515
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
1616
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
17+
"yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"},
1718
}

0 commit comments

Comments
 (0)