Files
PgTidy/pkg/format/dml_test.go
T
warkanum a98dee1877
CI / Test (push) Failing after 45s
CI / Build snapshot (push) Has been skipped
feat(dml): implement DML statement formatting and tests
* Add formatDML function for formatting top-level DML statements
* Introduce tests for various DML scenarios including SELECT, INSERT, UPDATE, and DELETE
* Enhance printer to handle DML statements correctly
2026-06-28 16:20:37 +02:00

242 lines
6.9 KiB
Go

package format
import (
"testing"
"git.warky.dev/wdevs/pgtidy/pkg/config"
"git.warky.dev/wdevs/pgtidy/pkg/parser"
)
// checkDML asserts that got is idempotent under formatting.
func checkDML(t *testing.T, name, got string) {
t.Helper()
twice := format(got)
if twice != got {
t.Errorf("%s: not idempotent\n--- once ---\n%s\n--- twice ---\n%s", name, got, twice)
}
}
func TestDMLSelectSingleCol(t *testing.T) {
src := "select a from t where x = 1;"
want := "SELECT a\nFROM t\nWHERE x = 1;\n"
got := format(src)
if got != want {
t.Errorf("select single col\n--- got ---\n%s\n--- want ---\n%s", got, want)
}
checkDML(t, "select single col", got)
if !semanticallyEqual(src, got) {
t.Errorf("select single col: formatting changed semantics")
}
}
func TestDMLSelectMultiCol(t *testing.T) {
src := "select a, b, c from t;"
want := "SELECT\n a\n ,b\n ,c\nFROM t;\n"
got := format(src)
if got != want {
t.Errorf("select multi col\n--- got ---\n%s\n--- want ---\n%s", got, want)
}
checkDML(t, "select multi col", got)
}
func TestDMLSelectClauses(t *testing.T) {
src := "select a, b from t where x = 1 group by a having count(*) > 1 order by b limit 10 offset 5;"
want := "SELECT\n a\n ,b\nFROM t\nWHERE x = 1\nGROUP BY a\nHAVING count(*) > 1\nORDER BY b\nLIMIT 10\nOFFSET 5;\n"
got := format(src)
if got != want {
t.Errorf("select clauses\n--- got ---\n%s\n--- want ---\n%s", got, want)
}
checkDML(t, "select clauses", got)
}
func TestDMLSelectJoins(t *testing.T) {
src := "select a.x, b.y from a join b on a.id = b.id left join c on b.id = c.id;"
want := "SELECT\n a.x\n ,b.y\nFROM a\nJOIN b ON a.id = b.id\nLEFT JOIN c ON b.id = c.id;\n"
got := format(src)
if got != want {
t.Errorf("select joins\n--- got ---\n%s\n--- want ---\n%s", got, want)
}
checkDML(t, "select joins", got)
}
func TestDMLInsert(t *testing.T) {
src := "insert into t (a, b) values (1, 2);"
// Table name before '(' looks like a function call to the spacing engine;
// accepted limitation — no space between table name and column list.
want := "INSERT INTO t(a, b)\nVALUES (1, 2);\n"
got := format(src)
if got != want {
t.Errorf("insert\n--- got ---\n%s\n--- want ---\n%s", got, want)
}
checkDML(t, "insert", got)
}
func TestDMLInsertSelect(t *testing.T) {
src := "insert into t (a, b) select x, y from s where z = 1;"
want := "INSERT INTO t(a, b)\nSELECT\n x\n ,y\nFROM s\nWHERE z = 1;\n"
got := format(src)
if got != want {
t.Errorf("insert select\n--- got ---\n%s\n--- want ---\n%s", got, want)
}
checkDML(t, "insert select", got)
}
func TestDMLUpdate(t *testing.T) {
src := "update t set a = 1, b = 2 where id = 3;"
want := "UPDATE t\nSET\n a = 1\n ,b = 2\nWHERE id = 3;\n"
got := format(src)
if got != want {
t.Errorf("update\n--- got ---\n%s\n--- want ---\n%s", got, want)
}
checkDML(t, "update", got)
}
func TestDMLUpdateSingleSet(t *testing.T) {
src := "update t set a = 1 where id = 2;"
want := "UPDATE t\nSET a = 1\nWHERE id = 2;\n"
got := format(src)
if got != want {
t.Errorf("update single set\n--- got ---\n%s\n--- want ---\n%s", got, want)
}
checkDML(t, "update single set", got)
}
func TestDMLDelete(t *testing.T) {
src := "delete from t where id = 1;"
want := "DELETE FROM t\nWHERE id = 1;\n"
got := format(src)
if got != want {
t.Errorf("delete\n--- got ---\n%s\n--- want ---\n%s", got, want)
}
checkDML(t, "delete", got)
}
func TestDMLWith(t *testing.T) {
src := "with cte as (select x from y) select x from cte;"
want := "WITH cte AS (\n SELECT x\n FROM y\n)\nSELECT x\nFROM cte;\n"
got := format(src)
if got != want {
t.Errorf("with cte\n--- got ---\n%s\n--- want ---\n%s", got, want)
}
checkDML(t, "with cte", got)
}
func TestDMLWithMultiple(t *testing.T) {
src := "with a as (select 1 as n), b as (select n + 1 from a) select n from b;"
want := "WITH\n" +
" a AS (\n" +
" SELECT 1 AS n\n" +
" )\n" +
" ,b AS (\n" +
" SELECT n + 1\n" +
" FROM a\n" +
" )\n" +
"SELECT n\n" +
"FROM b;\n"
got := format(src)
if got != want {
t.Errorf("with multiple ctes\n--- got ---\n%s\n--- want ---\n%s", got, want)
}
checkDML(t, "with multiple ctes", got)
}
func TestDMLSelectReturning(t *testing.T) {
src := "insert into t (a) values (1) returning id, a;"
want := "INSERT INTO t(a)\nVALUES (1)\nRETURNING\n id\n ,a;\n"
got := format(src)
if got != want {
t.Errorf("returning\n--- got ---\n%s\n--- want ---\n%s", got, want)
}
checkDML(t, "returning", got)
}
func TestDMLSelectUnion(t *testing.T) {
src := "select a from t1 union select a from t2;"
want := "SELECT a\nFROM t1\nUNION\nSELECT a\nFROM t2;\n"
got := format(src)
if got != want {
t.Errorf("union\n--- got ---\n%s\n--- want ---\n%s", got, want)
}
checkDML(t, "union", got)
}
func TestDMLTrailingCommaStyle(t *testing.T) {
st := config.Default()
st.Commas = config.CommaTrailing
src := "select a, b, c from t;"
got := File(parser.Parse(src), st)
want := "SELECT\n a,\n b,\n c\nFROM t;\n"
if got != want {
t.Errorf("trailing comma\n--- got ---\n%s\n--- want ---\n%s", got, want)
}
}
func TestDMLWithWriteable(t *testing.T) {
// Writeable CTE (UPDATE ... RETURNING) should format the inner DML too.
src := "with upd as (update t set a = 1 where id = 1 returning id, a) select id from upd;"
want := "WITH upd AS (\n" +
" UPDATE t\n" +
" SET a = 1\n" +
" WHERE id = 1\n" +
" RETURNING\n" +
" id\n" +
" ,a\n" +
")\n" +
"SELECT id\n" +
"FROM upd;\n"
got := format(src)
if got != want {
t.Errorf("writeable cte\n--- got ---\n%s\n--- want ---\n%s", got, want)
}
checkDML(t, "writeable cte", got)
}
func TestDMLWithMultiColBody(t *testing.T) {
// CTE body with multi-column SELECT should use leading-comma style.
src := "with cte as (select a, b, c from t where x = 1) select a from cte;"
want := "WITH cte AS (\n" +
" SELECT\n" +
" a\n" +
" ,b\n" +
" ,c\n" +
" FROM t\n" +
" WHERE x = 1\n" +
")\n" +
"SELECT a\n" +
"FROM cte;\n"
got := format(src)
if got != want {
t.Errorf("cte multi-col body\n--- got ---\n%s\n--- want ---\n%s", got, want)
}
checkDML(t, "cte multi-col body", got)
}
func TestDMLIdempotent(t *testing.T) {
cases := []string{
"SELECT\n a\n ,b\nFROM t\nWHERE x = 1;\n",
"UPDATE t\nSET\n a = 1\n ,b = 2\nWHERE id = 3;\n",
"DELETE FROM t\nWHERE id = 1;\n",
"WITH cte AS (\n SELECT x\n FROM y\n)\nSELECT x\nFROM cte;\n",
}
for _, src := range cases {
got := format(src)
if got != src {
t.Errorf("not idempotent:\n--- input ---\n%s\n--- got ---\n%s", src, got)
}
}
}
func TestCorpusUnaffectedByDML(t *testing.T) {
// Verify the corpus (which contains only CREATE FUNCTION) is not affected
// by the new DML formatting path.
src := "create function f(a int) returns void language sql as $$ select 1 $$;"
once := format(src)
twice := format(once)
if once != twice {
t.Errorf("create function not idempotent after DML change")
}
if !semanticallyEqual(src, once) {
t.Errorf("create function: DML formatter changed semantics")
}
}