* Add support for formatting subqueries with configurable placement and spacing. * Implement CASE expression formatting with options for wrapping and collapsing. * Introduce tests for subquery and CASE expression scenarios to ensure correctness.
614 lines
19 KiB
Go
614 lines
19 KiB
Go
package format
|
|
|
|
import (
|
|
"strings"
|
|
"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 TestDMLWhereAndOr(t *testing.T) {
|
|
// where_wrap=always should split AND/OR conditions onto separate lines.
|
|
src := "select a from t where x = 1 and y = 2 or z = 3;"
|
|
got := format(src)
|
|
want := "SELECT a\nFROM t\nWHERE\n x = 1\n AND y = 2\n OR z = 3;\n"
|
|
if got != want {
|
|
t.Errorf("where and/or\n--- got ---\n%s\n--- want ---\n%s", got, want)
|
|
}
|
|
checkDML(t, "where and/or", got)
|
|
}
|
|
|
|
func TestDMLIndentJoin(t *testing.T) {
|
|
st := config.Default()
|
|
st.IndentJoin = true
|
|
src := "select a from t join s on t.id = s.id;"
|
|
got := File(parser.Parse(src), st)
|
|
want := "SELECT a\nFROM t\n JOIN s ON t.id = s.id;\n"
|
|
if got != want {
|
|
t.Errorf("indent join\n--- got ---\n%s\n--- want ---\n%s", got, want)
|
|
}
|
|
// Idempotence with same config.
|
|
twice := File(parser.Parse(got), st)
|
|
if twice != got {
|
|
t.Errorf("indent join not idempotent:\n--- once ---\n%s\n--- twice ---\n%s", got, twice)
|
|
}
|
|
}
|
|
|
|
func TestDMLSetAlignEqual(t *testing.T) {
|
|
st := config.Default()
|
|
st.SetAlignEqual = true
|
|
src := "update t set a = 1, bb = 2, ccc = 3 where id = 1;"
|
|
got := File(parser.Parse(src), st)
|
|
// All = signs should align.
|
|
if got == "" {
|
|
t.Error("empty output")
|
|
}
|
|
// Idempotence.
|
|
twice := File(parser.Parse(got), st)
|
|
if twice != got {
|
|
t.Errorf("set_align_equal not idempotent:\n--- once ---\n%s\n--- twice ---\n%s", got, twice)
|
|
}
|
|
}
|
|
|
|
func TestDMLAlignParamTypes(t *testing.T) {
|
|
src := "create function f(in p_name text, in p_long_name integer, out p_result boolean) returns void language sql as $$ select 1 $$;"
|
|
got := format(src)
|
|
// p_name and p_long_name should have aligned types.
|
|
if got == "" {
|
|
t.Error("empty output")
|
|
}
|
|
twice := format(got)
|
|
if twice != got {
|
|
t.Errorf("align_param_types not idempotent:\n--- once ---\n%s\n--- twice ---\n%s", got, twice)
|
|
}
|
|
}
|
|
|
|
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")
|
|
}
|
|
}
|
|
|
|
// --- Subquery formatting ---
|
|
|
|
func TestDMLSubqueryDerivedTable(t *testing.T) {
|
|
src := "select a from (select x, y from t) s where s.x = 1;"
|
|
want := "SELECT a\n" +
|
|
"FROM (\n" +
|
|
" SELECT\n" +
|
|
" x\n" +
|
|
" ,y\n" +
|
|
" FROM t\n" +
|
|
") s\n" +
|
|
"WHERE s.x = 1;\n"
|
|
got := format(src)
|
|
if got != want {
|
|
t.Errorf("derived table\n--- got ---\n%s\n--- want ---\n%s", got, want)
|
|
}
|
|
checkDML(t, "derived table", got)
|
|
if !semanticallyEqual(src, got) {
|
|
t.Errorf("derived table: formatting changed semantics")
|
|
}
|
|
}
|
|
|
|
func TestDMLSubqueryScalarInSelect(t *testing.T) {
|
|
src := "select a, (select max(x) from t2) as m from t1;"
|
|
got := format(src)
|
|
if got == "" {
|
|
t.Error("empty output")
|
|
}
|
|
checkDML(t, "scalar subquery", got)
|
|
if !semanticallyEqual(src, got) {
|
|
t.Errorf("scalar subquery: formatting changed semantics")
|
|
}
|
|
}
|
|
|
|
func TestDMLSubqueryIn(t *testing.T) {
|
|
src := "select a from t where a in (select b from t2);"
|
|
want := "SELECT a\n" +
|
|
"FROM t\n" +
|
|
"WHERE a IN (\n" +
|
|
" SELECT b\n" +
|
|
" FROM t2\n" +
|
|
");\n"
|
|
got := format(src)
|
|
if got != want {
|
|
t.Errorf("in subquery\n--- got ---\n%s\n--- want ---\n%s", got, want)
|
|
}
|
|
checkDML(t, "in subquery", got)
|
|
}
|
|
|
|
func TestDMLSubqueryExists(t *testing.T) {
|
|
src := "select a from t where exists (select 1 from t2 where t2.a = t.a);"
|
|
got := format(src)
|
|
if got == "" {
|
|
t.Error("empty output")
|
|
}
|
|
checkDML(t, "exists subquery", got)
|
|
if !semanticallyEqual(src, got) {
|
|
t.Errorf("exists subquery: formatting changed semantics")
|
|
}
|
|
}
|
|
|
|
func TestDMLSubqueryInValueList(t *testing.T) {
|
|
// A plain value list must not be mistaken for a subquery.
|
|
src := "select a from t where a in (1, 2, 3);"
|
|
want := "SELECT a\nFROM t\nWHERE a IN (1, 2, 3);\n"
|
|
got := format(src)
|
|
if got != want {
|
|
t.Errorf("value list in()\n--- got ---\n%s\n--- want ---\n%s", got, want)
|
|
}
|
|
checkDML(t, "value list in()", got)
|
|
}
|
|
|
|
func TestDMLSubqueryPlacementConfig(t *testing.T) {
|
|
st := config.Default()
|
|
st.SubqueryContent = config.PlacementSameLine
|
|
st.SubqueryClosing = config.PlacementSameLine
|
|
src := "select a from t where a in (select b from t2);"
|
|
got := File(parser.Parse(src), st)
|
|
if got == "" {
|
|
t.Error("empty output")
|
|
}
|
|
twice := File(parser.Parse(got), st)
|
|
if twice != got {
|
|
t.Errorf("subquery placement config not idempotent:\n--- once ---\n%s\n--- twice ---\n%s", got, twice)
|
|
}
|
|
}
|
|
|
|
func TestDMLSubquerySpaceBeforeParen(t *testing.T) {
|
|
st := config.Default()
|
|
st.SubquerySpaceBeforeParen = true
|
|
src := "select array(select x from t) from t2;"
|
|
got := File(parser.Parse(src), st)
|
|
want := "SELECT ARRAY (\n SELECT x\n FROM t\n)\nFROM t2;\n"
|
|
if got != want {
|
|
t.Errorf("subquery_space_before_paren\n--- got ---\n%s\n--- want ---\n%s", got, want)
|
|
}
|
|
twice := File(parser.Parse(got), st)
|
|
if twice != got {
|
|
t.Errorf("subquery_space_before_paren not idempotent:\n--- once ---\n%s\n--- twice ---\n%s", got, twice)
|
|
}
|
|
}
|
|
|
|
func TestDMLCTEUsesSubqueryConfig(t *testing.T) {
|
|
// CTE bodies should honor the same subquery_* settings, not a hardcoded layout.
|
|
st := config.Default()
|
|
st.SubqueryOpening = config.PlacementNewLine
|
|
src := "with cte as (select x from y) select x from cte;"
|
|
got := File(parser.Parse(src), st)
|
|
want := "WITH cte AS\n(\n SELECT x\n FROM y\n)\nSELECT x\nFROM cte;\n"
|
|
if got != want {
|
|
t.Errorf("cte subquery_opening=new_line\n--- got ---\n%s\n--- want ---\n%s", got, want)
|
|
}
|
|
twice := File(parser.Parse(got), st)
|
|
if twice != got {
|
|
t.Errorf("cte subquery_opening not idempotent:\n--- once ---\n%s\n--- twice ---\n%s", got, twice)
|
|
}
|
|
}
|
|
|
|
// --- INSERT VALUES collapse ---
|
|
|
|
func TestDMLInsertValuesCollapseDefault(t *testing.T) {
|
|
// insert_collapse_values defaults to true: multiple rows stay on one line.
|
|
src := "insert into t (a, b) values (1, 2), (3, 4), (5, 6);"
|
|
want := "INSERT INTO t(a, b)\nVALUES (1, 2), (3, 4), (5, 6);\n"
|
|
got := format(src)
|
|
if got != want {
|
|
t.Errorf("values collapse default\n--- got ---\n%s\n--- want ---\n%s", got, want)
|
|
}
|
|
checkDML(t, "values collapse default", got)
|
|
}
|
|
|
|
func TestDMLInsertValuesNoCollapse(t *testing.T) {
|
|
st := config.Default()
|
|
st.InsertCollapseValues = false
|
|
src := "insert into t (a, b) values (1, 2), (3, 4), (5, 6);"
|
|
want := "INSERT INTO t(a, b)\n" +
|
|
"VALUES\n" +
|
|
" (1, 2)\n" +
|
|
" ,(3, 4)\n" +
|
|
" ,(5, 6);\n"
|
|
got := File(parser.Parse(src), st)
|
|
if got != want {
|
|
t.Errorf("values no collapse\n--- got ---\n%s\n--- want ---\n%s", got, want)
|
|
}
|
|
twice := File(parser.Parse(got), st)
|
|
if twice != got {
|
|
t.Errorf("values no collapse not idempotent:\n--- once ---\n%s\n--- twice ---\n%s", got, twice)
|
|
}
|
|
if !semanticallyEqual(src, got) {
|
|
t.Errorf("values no collapse: formatting changed semantics")
|
|
}
|
|
}
|
|
|
|
func TestDMLInsertValuesSingleRowUnaffected(t *testing.T) {
|
|
// A single-row VALUES is unaffected by insert_collapse_values either way.
|
|
st := config.Default()
|
|
st.InsertCollapseValues = false
|
|
src := "insert into t (a, b) values (1, 2);"
|
|
want := "INSERT INTO t(a, b)\nVALUES (1, 2);\n"
|
|
got := File(parser.Parse(src), st)
|
|
if got != want {
|
|
t.Errorf("single row values\n--- got ---\n%s\n--- want ---\n%s", got, want)
|
|
}
|
|
}
|
|
|
|
// --- CASE expression formatting ---
|
|
|
|
func TestDMLCaseInlineDefault(t *testing.T) {
|
|
src := "select case when a = 1 then 'one' when a = 2 then 'two' else 'other' end as label from t;"
|
|
want := "SELECT CASE WHEN a = 1 THEN 'one' WHEN a = 2 THEN 'two' ELSE 'other' END AS label\nFROM t;\n"
|
|
got := format(src)
|
|
if got != want {
|
|
t.Errorf("case inline default\n--- got ---\n%s\n--- want ---\n%s", got, want)
|
|
}
|
|
checkDML(t, "case inline default", got)
|
|
if !semanticallyEqual(src, got) {
|
|
t.Errorf("case inline default: formatting changed semantics")
|
|
}
|
|
}
|
|
|
|
func TestDMLCaseWhenWrap(t *testing.T) {
|
|
st := config.Default()
|
|
st.CaseWhenWrap = true
|
|
src := "select case when a = 1 then 'one' when a = 2 then 'two' else 'other' end as label from t;"
|
|
want := "SELECT CASE\n" +
|
|
" WHEN a = 1 THEN 'one'\n" +
|
|
" WHEN a = 2 THEN 'two'\n" +
|
|
" ELSE 'other'\n" +
|
|
"END AS label\n" +
|
|
"FROM t;\n"
|
|
got := File(parser.Parse(src), st)
|
|
if got != want {
|
|
t.Errorf("case when_wrap\n--- got ---\n%s\n--- want ---\n%s", got, want)
|
|
}
|
|
twice := File(parser.Parse(got), st)
|
|
if twice != got {
|
|
t.Errorf("case when_wrap not idempotent:\n--- once ---\n%s\n--- twice ---\n%s", got, twice)
|
|
}
|
|
if !semanticallyEqual(src, got) {
|
|
t.Errorf("case when_wrap: formatting changed semantics")
|
|
}
|
|
}
|
|
|
|
func TestDMLCaseEndSameLine(t *testing.T) {
|
|
st := config.Default()
|
|
st.CaseWhenWrap = true
|
|
st.CaseEnd = config.PlacementSameLine
|
|
src := "select case when a = 1 then 'one' else 'other' end as label from t;"
|
|
want := "SELECT CASE\n" +
|
|
" WHEN a = 1 THEN 'one'\n" +
|
|
" ELSE 'other' END AS label\n" +
|
|
"FROM t;\n"
|
|
got := File(parser.Parse(src), st)
|
|
if got != want {
|
|
t.Errorf("case_end same_line\n--- got ---\n%s\n--- want ---\n%s", got, want)
|
|
}
|
|
twice := File(parser.Parse(got), st)
|
|
if twice != got {
|
|
t.Errorf("case_end same_line not idempotent:\n--- once ---\n%s\n--- twice ---\n%s", got, twice)
|
|
}
|
|
}
|
|
|
|
func TestDMLCaseCollapseShort(t *testing.T) {
|
|
// case_collapse keeps a short CASE on one line even with case_when_wrap set.
|
|
st := config.Default()
|
|
st.CaseWhenWrap = true
|
|
st.CaseCollapse = true
|
|
src := "select case when a = 1 then 'x' else 'y' end from t;"
|
|
want := "SELECT CASE WHEN a = 1 THEN 'x' ELSE 'y' END\nFROM t;\n"
|
|
got := File(parser.Parse(src), st)
|
|
if got != want {
|
|
t.Errorf("case_collapse short\n--- got ---\n%s\n--- want ---\n%s", got, want)
|
|
}
|
|
twice := File(parser.Parse(got), st)
|
|
if twice != got {
|
|
t.Errorf("case_collapse short not idempotent:\n--- once ---\n%s\n--- twice ---\n%s", got, twice)
|
|
}
|
|
}
|
|
|
|
func TestDMLCaseCollapseLongStillWraps(t *testing.T) {
|
|
// case_collapse only keeps CASE inline when it is short; a long CASE still wraps.
|
|
st := config.Default()
|
|
st.CaseWhenWrap = true
|
|
st.CaseCollapse = true
|
|
src := "select case when a = 1 then 'a fairly long result value one' " +
|
|
"when a = 2 then 'a fairly long result value two' else 'a fairly long default value' end from t;"
|
|
got := File(parser.Parse(src), st)
|
|
if !strings.Contains(got, "\n WHEN a = 1") {
|
|
t.Errorf("case_collapse long: expected wrapped WHEN branches, got:\n%s", got)
|
|
}
|
|
twice := File(parser.Parse(got), st)
|
|
if twice != got {
|
|
t.Errorf("case_collapse long not idempotent:\n--- once ---\n%s\n--- twice ---\n%s", got, twice)
|
|
}
|
|
}
|
|
|
|
func TestDMLCaseNestedInSubquery(t *testing.T) {
|
|
src := "select a from (select case when x = 1 then 'y' else 'n' end as c from t) s;"
|
|
got := format(src)
|
|
if got == "" {
|
|
t.Error("empty output")
|
|
}
|
|
checkDML(t, "case nested in subquery", got)
|
|
if !semanticallyEqual(src, got) {
|
|
t.Errorf("case nested in subquery: formatting changed semantics")
|
|
}
|
|
}
|
|
|
|
func TestDMLSubqueryNestedInCase(t *testing.T) {
|
|
src := "select case when exists (select 1 from t2 where t2.a = t1.a) then 'y' else 'n' end from t1;"
|
|
got := format(src)
|
|
if got == "" {
|
|
t.Error("empty output")
|
|
}
|
|
checkDML(t, "subquery nested in case", got)
|
|
if !semanticallyEqual(src, got) {
|
|
t.Errorf("subquery nested in case: formatting changed semantics")
|
|
}
|
|
}
|
|
|
|
func TestDMLCaseSimpleForm(t *testing.T) {
|
|
// Simple CASE (with an operand) must round-trip too.
|
|
src := "select case a when 1 then 'one' when 2 then 'two' else 'other' end from t;"
|
|
want := "SELECT CASE a WHEN 1 THEN 'one' WHEN 2 THEN 'two' ELSE 'other' END\nFROM t;\n"
|
|
got := format(src)
|
|
if got != want {
|
|
t.Errorf("simple case\n--- got ---\n%s\n--- want ---\n%s", got, want)
|
|
}
|
|
checkDML(t, "simple case", got)
|
|
}
|
|
|
|
// --- record_space_before_paren ---
|
|
|
|
func TestDMLRecordSpaceBeforeParen(t *testing.T) {
|
|
src := "select row(1, 2) from t;"
|
|
|
|
got := format(src)
|
|
want := "SELECT ROW(1, 2)\nFROM t;\n"
|
|
if got != want {
|
|
t.Errorf("row() default\n--- got ---\n%s\n--- want ---\n%s", got, want)
|
|
}
|
|
|
|
st := config.Default()
|
|
st.RecordSpaceBeforeParen = true
|
|
gotSpaced := File(parser.Parse(src), st)
|
|
wantSpaced := "SELECT ROW (1, 2)\nFROM t;\n"
|
|
if gotSpaced != wantSpaced {
|
|
t.Errorf("row() space_before_paren\n--- got ---\n%s\n--- want ---\n%s", gotSpaced, wantSpaced)
|
|
}
|
|
twice := File(parser.Parse(gotSpaced), st)
|
|
if twice != gotSpaced {
|
|
t.Errorf("row() space_before_paren not idempotent:\n--- once ---\n%s\n--- twice ---\n%s", gotSpaced, twice)
|
|
}
|
|
}
|