From 0634223479832206129dd909c284ad3a9f7fc12e Mon Sep 17 00:00:00 2001 From: Andrew Nicoll Date: Thu, 30 Oct 2025 15:11:17 +1030 Subject: [PATCH 1/4] feat: improve the SQL output for CreateView in spansql --- spanner/spansql/sql.go | 81 +++++++++- spanner/spansql/sql_test.go | 299 +++++++++++++++++++++++++++++++++++- 2 files changed, 371 insertions(+), 9 deletions(-) diff --git a/spanner/spansql/sql.go b/spanner/spansql/sql.go index 9b6718133a96..d35a71465a71 100644 --- a/spanner/spansql/sql.go +++ b/spanner/spansql/sql.go @@ -171,12 +171,83 @@ func (cp CreateProtoBundle) SQL() string { } func (cv CreateView) SQL() string { - str := "CREATE" + var sb strings.Builder + sb.WriteString("CREATE") if cv.OrReplace { - str += " OR REPLACE" + sb.WriteString(" OR REPLACE") + } + sb.WriteString(" VIEW ") + sb.WriteString(cv.Name.SQL()) + sb.WriteString(" SQL SECURITY ") + sb.WriteString(cv.SecurityType.SQL()) + sb.WriteString(" AS SELECT\n") + + for i, expr := range cv.Query.Select.List { + sb.WriteString("\t") + sb.WriteString(expr.SQL()) + // add alias if available. + if cv.Query.Select.ListAliases != nil && i < len(cv.Query.Select.ListAliases) && cv.Query.Select.ListAliases[i] != "" { + sb.WriteString(" AS ") + sb.WriteString(cv.Query.Select.ListAliases[i].SQL()) + } + // Add a newline after each expression + if i < len(cv.Query.Select.List)-1 { + sb.WriteString(",\n") + } else { + sb.WriteString("\n") + } } - str += " VIEW " + cv.Name.SQL() + " SQL SECURITY " + cv.SecurityType.SQL() + " AS " + cv.Query.SQL() - return str + + // FROM clause + if len(cv.Query.Select.From) > 0 { + sb.WriteString("FROM ") + for i, f := range cv.Query.Select.From { + if i > 0 { + sb.WriteString(", ") + } + sb.WriteString(f.SQL()) + } + } + + // WHERE clause + if cv.Query.Select.Where != nil { + sb.WriteString("\nWHERE ") + sb.WriteString(cv.Query.Select.Where.SQL()) + } + + // GROUP BY clause + if len(cv.Query.Select.GroupBy) > 0 { + sb.WriteString("\nGROUP BY ") + for i, gb := range cv.Query.Select.GroupBy { + if i > 0 { + sb.WriteString(", ") + } + sb.WriteString(gb.SQL()) + } + } + + // ORDER BY clause + if cv.Query.Order != nil { + sb.WriteString("\nORDER BY ") + for i, o := range cv.Query.Order { + if i > 0 { + sb.WriteString(", ") + } + sb.WriteString(o.SQL()) + } + } + + // LIMIT/OFFSET clauses + if cv.Query.Limit != nil { + sb.WriteString("\nLIMIT ") + sb.WriteString(cv.Query.Limit.SQL()) + if cv.Query.Offset != nil { + sb.WriteString(" OFFSET ") + sb.WriteString(cv.Query.Offset.SQL()) + } + } + + return sb.String() } func (st SecurityType) SQL() string { @@ -918,7 +989,7 @@ func (sft SelectFromTable) SQL() string { func (sfj SelectFromJoin) SQL() string { // TODO: The grammar permits arbitrary nesting. Does this need to add parens? - str := sfj.LHS.SQL() + " " + joinTypes[sfj.Type] + " JOIN " + str := sfj.LHS.SQL() + "\n" + joinTypes[sfj.Type] + " JOIN " // TODO: hints go here str += sfj.RHS.SQL() if sfj.On != nil { diff --git a/spanner/spansql/sql_test.go b/spanner/spansql/sql_test.go index 375309cc149c..d89b84aa02f1 100644 --- a/spanner/spansql/sql_test.go +++ b/spanner/spansql/sql_test.go @@ -234,7 +234,12 @@ func TestSQL(t *testing.T) { }, Position: line(1), }, - "CREATE OR REPLACE VIEW SingersView SQL SECURITY INVOKER AS SELECT SingerId, FullName, Picture FROM Singers ORDER BY LastName, FirstName", + `CREATE OR REPLACE VIEW SingersView SQL SECURITY INVOKER AS SELECT + SingerId, + FullName, + Picture +FROM Singers +ORDER BY LastName, FirstName`, reparseDDL, }, { @@ -252,7 +257,290 @@ func TestSQL(t *testing.T) { }, Position: line(1), }, - "CREATE VIEW vname SQL SECURITY DEFINER AS SELECT cname FROM tname", + `CREATE VIEW vname SQL SECURITY DEFINER AS SELECT + cname +FROM tname`, + reparseDDL, + }, + { + &CreateView{ + Name: "ViewWithWhere", + OrReplace: false, + SecurityType: Invoker, + Query: Query{ + Select: Select{ + List: []Expr{ID("id"), ID("name")}, + From: []SelectFrom{SelectFromTable{ + Table: "users", + }}, + Where: ComparisonOp{ + Op: Gt, + LHS: ID("age"), + RHS: IntegerLiteral(18), + }, + }, + }, + Position: line(1), + }, + `CREATE VIEW ViewWithWhere SQL SECURITY INVOKER AS SELECT + id, + name +FROM users +WHERE age > 18`, + reparseDDL, + }, + { + &CreateView{ + Name: "ViewWithGroupBy", + OrReplace: true, + SecurityType: Definer, + Query: Query{ + Select: Select{ + List: []Expr{ + ID("department"), + Func{Name: "COUNT", Args: []Expr{Star}}, + }, + From: []SelectFrom{SelectFromTable{ + Table: "employees", + }}, + GroupBy: []Expr{ID("department")}, + }, + }, + Position: line(1), + }, + `CREATE OR REPLACE VIEW ViewWithGroupBy SQL SECURITY DEFINER AS SELECT + department, + COUNT(*) +FROM employees +GROUP BY department`, + reparseDDL, + }, + { + &CreateView{ + Name: "ViewWithMultiGroupBy", + OrReplace: false, + SecurityType: Invoker, + Query: Query{ + Select: Select{ + List: []Expr{ + ID("region"), + ID("department"), + Func{Name: "SUM", Args: []Expr{ID("salary")}}, + }, + From: []SelectFrom{SelectFromTable{ + Table: "employees", + }}, + GroupBy: []Expr{ID("region"), ID("department")}, + }, + }, + Position: line(1), + }, + `CREATE VIEW ViewWithMultiGroupBy SQL SECURITY INVOKER AS SELECT + region, + department, + SUM(salary) +FROM employees +GROUP BY region, department`, + reparseDDL, + }, + { + &CreateView{ + Name: "ViewWithLimit", + OrReplace: false, + SecurityType: Invoker, + Query: Query{ + Select: Select{ + List: []Expr{ID("id"), ID("name")}, + From: []SelectFrom{SelectFromTable{ + Table: "products", + }}, + }, + Limit: IntegerLiteral(10), + }, + Position: line(1), + }, + `CREATE VIEW ViewWithLimit SQL SECURITY INVOKER AS SELECT + id, + name +FROM products +LIMIT 10`, + reparseDDL, + }, + { + &CreateView{ + Name: "ViewWithLimitOffset", + OrReplace: true, + SecurityType: Definer, + Query: Query{ + Select: Select{ + List: []Expr{ID("id"), ID("title")}, + From: []SelectFrom{SelectFromTable{ + Table: "articles", + }}, + }, + Order: []Order{ + {Expr: ID("created_at"), Desc: true}, + }, + Limit: IntegerLiteral(20), + Offset: IntegerLiteral(5), + }, + Position: line(1), + }, + `CREATE OR REPLACE VIEW ViewWithLimitOffset SQL SECURITY DEFINER AS SELECT + id, + title +FROM articles +ORDER BY created_at DESC +LIMIT 20 OFFSET 5`, + reparseDDL, + }, + { + &CreateView{ + Name: "ViewWithAllClauses", + OrReplace: true, + SecurityType: Invoker, + Query: Query{ + Select: Select{ + List: []Expr{ + ID("category"), + Func{Name: "COUNT", Args: []Expr{ID("id")}}, + Func{Name: "AVG", Args: []Expr{ID("price")}}, + }, + ListAliases: []ID{"", "total_count", "avg_price"}, + From: []SelectFrom{SelectFromTable{ + Table: "products", + }}, + Where: ComparisonOp{ + Op: Gt, + LHS: ID("price"), + RHS: IntegerLiteral(0), + }, + GroupBy: []Expr{ID("category")}, + }, + Order: []Order{ + {Expr: ID("category")}, + }, + Limit: IntegerLiteral(100), + Offset: IntegerLiteral(10), + }, + Position: line(1), + }, + `CREATE OR REPLACE VIEW ViewWithAllClauses SQL SECURITY INVOKER AS SELECT + category, + COUNT(id) AS total_count, + AVG(price) AS avg_price +FROM products +WHERE price > 0 +GROUP BY category +ORDER BY category +LIMIT 100 OFFSET 10`, + reparseDDL, + }, + { + &CreateView{ + Name: "ComplexViewWithJoins", + OrReplace: true, + SecurityType: Definer, + Query: Query{ + Select: Select{ + List: []Expr{ + PathExp{"u", "id"}, + PathExp{"u", "name"}, + PathExp{"o", "order_id"}, + PathExp{"o", "total"}, + PathExp{"p", "product_name"}, + Func{Name: "COUNT", Args: []Expr{PathExp{"oi", "item_id"}}}, + }, + ListAliases: []ID{"user_id", "user_name", "", "order_total", "", "item_count"}, + From: []SelectFrom{ + SelectFromJoin{ + Type: InnerJoin, + LHS: SelectFromJoin{ + Type: LeftJoin, + LHS: SelectFromJoin{ + Type: InnerJoin, + LHS: SelectFromTable{ + Table: "users", + Alias: "u", + }, + RHS: SelectFromTable{ + Table: "orders", + Alias: "o", + }, + On: ComparisonOp{ + Op: Eq, + LHS: PathExp{"u", "id"}, + RHS: PathExp{"o", "user_id"}, + }, + }, + RHS: SelectFromTable{ + Table: "order_items", + Alias: "oi", + }, + On: ComparisonOp{ + Op: Eq, + LHS: PathExp{"o", "order_id"}, + RHS: PathExp{"oi", "order_id"}, + }, + }, + RHS: SelectFromTable{ + Table: "products", + Alias: "p", + }, + On: ComparisonOp{ + Op: Eq, + LHS: PathExp{"oi", "product_id"}, + RHS: PathExp{"p", "id"}, + }, + }, + }, + Where: LogicalOp{ + Op: And, + LHS: LogicalOp{ + Op: And, + LHS: ComparisonOp{ + Op: Gt, + LHS: PathExp{"o", "total"}, + RHS: IntegerLiteral(100), + }, + RHS: ComparisonOp{ + Op: Eq, + LHS: PathExp{"u", "status"}, + RHS: StringLiteral("active"), + }, + }, + RHS: ComparisonOp{ + Op: Ge, + LHS: PathExp{"p", "price"}, + RHS: IntegerLiteral(10), + }, + }, + GroupBy: []Expr{PathExp{"u", "id"}, PathExp{"u", "name"}, PathExp{"o", "order_id"}, PathExp{"o", "total"}, PathExp{"p", "product_name"}}, + }, + Order: []Order{ + {Expr: PathExp{"o", "total"}, Desc: true}, + {Expr: PathExp{"u", "name"}}, + }, + Limit: IntegerLiteral(50), + Offset: IntegerLiteral(0), + }, + Position: line(1), + }, + `CREATE OR REPLACE VIEW ComplexViewWithJoins SQL SECURITY DEFINER AS SELECT + u.id AS user_id, + u.name AS user_name, + o.order_id, + o.total AS order_total, + p.product_name, + COUNT(oi.item_id) AS item_count +FROM users AS u +INNER JOIN orders AS o ON u.id = o.user_id +LEFT JOIN order_items AS oi ON o.order_id = oi.order_id +INNER JOIN products AS p ON oi.product_id = p.id +WHERE o.total > 100 AND u.status = "active" AND p.price >= 10 +GROUP BY u.id, u.name, o.order_id, o.total, p.product_name +ORDER BY o.total DESC, u.name +LIMIT 50 OFFSET 0`, reparseDDL, }, { @@ -1411,7 +1699,8 @@ func TestSQL(t *testing.T) { }, }, }, - "SELECT A, B FROM Table1 INNER JOIN Table2 ON Table1.A = Table2.A", + `SELECT A, B FROM Table1 +INNER JOIN Table2 ON Table1.A = Table2.A`, reparseQuery, }, { @@ -1439,7 +1728,9 @@ func TestSQL(t *testing.T) { }, }, }, - "SELECT A, B FROM Table1 INNER JOIN Table2 ON Table1.A = Table2.A INNER JOIN Table3 USING (X)", + `SELECT A, B FROM Table1 +INNER JOIN Table2 ON Table1.A = Table2.A +INNER JOIN Table3 USING (X)`, reparseQuery, }, { From 52ef58ee600927bccc5d81fda17f8dd8b9e445d9 Mon Sep 17 00:00:00 2001 From: Andrew Nicoll Date: Thu, 30 Oct 2025 15:22:45 +1030 Subject: [PATCH 2/4] chore: improve changes --- spanner/spansql/sql.go | 47 +++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/spanner/spansql/sql.go b/spanner/spansql/sql.go index d35a71465a71..95199813e1ff 100644 --- a/spanner/spansql/sql.go +++ b/spanner/spansql/sql.go @@ -180,18 +180,25 @@ func (cv CreateView) SQL() string { sb.WriteString(cv.Name.SQL()) sb.WriteString(" SQL SECURITY ") sb.WriteString(cv.SecurityType.SQL()) - sb.WriteString(" AS SELECT\n") + sb.WriteString(" AS ") + writeFormattedQuery(&sb, &cv.Query) + return sb.String() +} - for i, expr := range cv.Query.Select.List { +func writeFormattedQuery(sb *strings.Builder, q *Query) { + sb.WriteString("SELECT\n") + + // SELECT list with aliases + for i, expr := range q.Select.List { sb.WriteString("\t") sb.WriteString(expr.SQL()) - // add alias if available. - if cv.Query.Select.ListAliases != nil && i < len(cv.Query.Select.ListAliases) && cv.Query.Select.ListAliases[i] != "" { + // Add alias if available (check bounds safely) + if len(q.Select.ListAliases) > i && q.Select.ListAliases[i] != "" { sb.WriteString(" AS ") - sb.WriteString(cv.Query.Select.ListAliases[i].SQL()) + sb.WriteString(q.Select.ListAliases[i].SQL()) } - // Add a newline after each expression - if i < len(cv.Query.Select.List)-1 { + // Add comma and newline, except after last item + if i < len(q.Select.List)-1 { sb.WriteString(",\n") } else { sb.WriteString("\n") @@ -199,9 +206,9 @@ func (cv CreateView) SQL() string { } // FROM clause - if len(cv.Query.Select.From) > 0 { + if len(q.Select.From) > 0 { sb.WriteString("FROM ") - for i, f := range cv.Query.Select.From { + for i, f := range q.Select.From { if i > 0 { sb.WriteString(", ") } @@ -210,15 +217,15 @@ func (cv CreateView) SQL() string { } // WHERE clause - if cv.Query.Select.Where != nil { + if q.Select.Where != nil { sb.WriteString("\nWHERE ") - sb.WriteString(cv.Query.Select.Where.SQL()) + sb.WriteString(q.Select.Where.SQL()) } // GROUP BY clause - if len(cv.Query.Select.GroupBy) > 0 { + if len(q.Select.GroupBy) > 0 { sb.WriteString("\nGROUP BY ") - for i, gb := range cv.Query.Select.GroupBy { + for i, gb := range q.Select.GroupBy { if i > 0 { sb.WriteString(", ") } @@ -227,9 +234,9 @@ func (cv CreateView) SQL() string { } // ORDER BY clause - if cv.Query.Order != nil { + if len(q.Order) > 0 { sb.WriteString("\nORDER BY ") - for i, o := range cv.Query.Order { + for i, o := range q.Order { if i > 0 { sb.WriteString(", ") } @@ -238,16 +245,14 @@ func (cv CreateView) SQL() string { } // LIMIT/OFFSET clauses - if cv.Query.Limit != nil { + if q.Limit != nil { sb.WriteString("\nLIMIT ") - sb.WriteString(cv.Query.Limit.SQL()) - if cv.Query.Offset != nil { + sb.WriteString(q.Limit.SQL()) + if q.Offset != nil { sb.WriteString(" OFFSET ") - sb.WriteString(cv.Query.Offset.SQL()) + sb.WriteString(q.Offset.SQL()) } } - - return sb.String() } func (st SecurityType) SQL() string { From 18bba659e78aaf209c6a2293243c06ce211f4a9f Mon Sep 17 00:00:00 2001 From: Andrew Nicoll Date: Thu, 30 Oct 2025 17:19:20 +1030 Subject: [PATCH 3/4] chore: pr comment suggestion fixes --- spanner/spansql/sql.go | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/spanner/spansql/sql.go b/spanner/spansql/sql.go index 95199813e1ff..a2d993240feb 100644 --- a/spanner/spansql/sql.go +++ b/spanner/spansql/sql.go @@ -185,6 +185,15 @@ func (cv CreateView) SQL() string { return sb.String() } +func writeSQLList[T interface{ SQL() string }](sb *strings.Builder, items []T) { + for i, item := range items { + if i > 0 { + sb.WriteString(", ") + } + sb.WriteString(item.SQL()) + } +} + func writeFormattedQuery(sb *strings.Builder, q *Query) { sb.WriteString("SELECT\n") @@ -208,12 +217,7 @@ func writeFormattedQuery(sb *strings.Builder, q *Query) { // FROM clause if len(q.Select.From) > 0 { sb.WriteString("FROM ") - for i, f := range q.Select.From { - if i > 0 { - sb.WriteString(", ") - } - sb.WriteString(f.SQL()) - } + writeSQLList(sb, q.Select.From) } // WHERE clause @@ -225,23 +229,13 @@ func writeFormattedQuery(sb *strings.Builder, q *Query) { // GROUP BY clause if len(q.Select.GroupBy) > 0 { sb.WriteString("\nGROUP BY ") - for i, gb := range q.Select.GroupBy { - if i > 0 { - sb.WriteString(", ") - } - sb.WriteString(gb.SQL()) - } + writeSQLList(sb, q.Select.GroupBy) } // ORDER BY clause if len(q.Order) > 0 { sb.WriteString("\nORDER BY ") - for i, o := range q.Order { - if i > 0 { - sb.WriteString(", ") - } - sb.WriteString(o.SQL()) - } + writeSQLList(sb, q.Order) } // LIMIT/OFFSET clauses From 23c416c828017f06b5b9b116912dbe6a592fe643 Mon Sep 17 00:00:00 2001 From: Andrew Nicoll Date: Thu, 30 Oct 2025 21:01:06 +1030 Subject: [PATCH 4/4] chore: implement formatting fix for all SQL --- spanner/spansql/sql.go | 107 ++++++++---------------- spanner/spansql/sql_test.go | 158 ++++++++++++++++++++++++++++++++---- 2 files changed, 174 insertions(+), 91 deletions(-) diff --git a/spanner/spansql/sql.go b/spanner/spansql/sql.go index a2d993240feb..79198d54dda9 100644 --- a/spanner/spansql/sql.go +++ b/spanner/spansql/sql.go @@ -181,74 +181,10 @@ func (cv CreateView) SQL() string { sb.WriteString(" SQL SECURITY ") sb.WriteString(cv.SecurityType.SQL()) sb.WriteString(" AS ") - writeFormattedQuery(&sb, &cv.Query) + cv.Query.addSQL(&sb) return sb.String() } -func writeSQLList[T interface{ SQL() string }](sb *strings.Builder, items []T) { - for i, item := range items { - if i > 0 { - sb.WriteString(", ") - } - sb.WriteString(item.SQL()) - } -} - -func writeFormattedQuery(sb *strings.Builder, q *Query) { - sb.WriteString("SELECT\n") - - // SELECT list with aliases - for i, expr := range q.Select.List { - sb.WriteString("\t") - sb.WriteString(expr.SQL()) - // Add alias if available (check bounds safely) - if len(q.Select.ListAliases) > i && q.Select.ListAliases[i] != "" { - sb.WriteString(" AS ") - sb.WriteString(q.Select.ListAliases[i].SQL()) - } - // Add comma and newline, except after last item - if i < len(q.Select.List)-1 { - sb.WriteString(",\n") - } else { - sb.WriteString("\n") - } - } - - // FROM clause - if len(q.Select.From) > 0 { - sb.WriteString("FROM ") - writeSQLList(sb, q.Select.From) - } - - // WHERE clause - if q.Select.Where != nil { - sb.WriteString("\nWHERE ") - sb.WriteString(q.Select.Where.SQL()) - } - - // GROUP BY clause - if len(q.Select.GroupBy) > 0 { - sb.WriteString("\nGROUP BY ") - writeSQLList(sb, q.Select.GroupBy) - } - - // ORDER BY clause - if len(q.Order) > 0 { - sb.WriteString("\nORDER BY ") - writeSQLList(sb, q.Order) - } - - // LIMIT/OFFSET clauses - if q.Limit != nil { - sb.WriteString("\nLIMIT ") - sb.WriteString(q.Limit.SQL()) - if q.Offset != nil { - sb.WriteString(" OFFSET ") - sb.WriteString(q.Offset.SQL()) - } - } -} - func (st SecurityType) SQL() string { switch st { case Invoker: @@ -908,8 +844,10 @@ func (kp KeyPart) SQL() string { func (q Query) SQL() string { return buildSQL(q) } func (q Query) addSQL(sb *strings.Builder) { q.Select.addSQL(sb) + + // ORDER BY clause if len(q.Order) > 0 { - sb.WriteString(" ORDER BY ") + sb.WriteString("\nORDER BY ") for i, o := range q.Order { if i > 0 { sb.WriteString(", ") @@ -917,8 +855,10 @@ func (q Query) addSQL(sb *strings.Builder) { o.addSQL(sb) } } + + // LIMIT/OFFSET clauses if q.Limit != nil { - sb.WriteString(" LIMIT ") + sb.WriteString("\nLIMIT ") sb.WriteString(q.Limit.SQL()) if q.Offset != nil { sb.WriteString(" OFFSET ") @@ -929,14 +869,17 @@ func (q Query) addSQL(sb *strings.Builder) { func (sel Select) SQL() string { return buildSQL(sel) } func (sel Select) addSQL(sb *strings.Builder) { - sb.WriteString("SELECT ") + sb.WriteString("SELECT") if sel.Distinct { - sb.WriteString("DISTINCT ") + sb.WriteString(" DISTINCT") } + + // SELECT list with aliases for i, e := range sel.List { - if i > 0 { - sb.WriteString(", ") + if i == 0 { + sb.WriteString("\n") } + sb.WriteString("\t") e.addSQL(sb) if len(sel.ListAliases) > 0 { alias := sel.ListAliases[i] @@ -945,9 +888,15 @@ func (sel Select) addSQL(sb *strings.Builder) { sb.WriteString(alias.SQL()) } } + // Add comma and newline, except after last item + if i < len(sel.List)-1 { + sb.WriteString(",\n") + } } + + // FROM clause if len(sel.From) > 0 { - sb.WriteString(" FROM ") + sb.WriteString("\nFROM ") for i, f := range sel.From { if i > 0 { sb.WriteString(", ") @@ -955,14 +904,24 @@ func (sel Select) addSQL(sb *strings.Builder) { sb.WriteString(f.SQL()) } } + + // WHERE clause if sel.Where != nil { - sb.WriteString(" WHERE ") + sb.WriteString("\nWHERE ") sel.Where.addSQL(sb) } + + // GROUP BY clause if len(sel.GroupBy) > 0 { - sb.WriteString(" GROUP BY ") + sb.WriteString("\nGROUP BY ") addExprList(sb, sel.GroupBy, ", ") } + + // TODO: HAVING clause when supported + // if sel.Having != nil { + // sb.WriteString("\nHAVING ") + // sel.Having.addSQL(sb) + // } } func (sft SelectFromTable) SQL() string { diff --git a/spanner/spansql/sql_test.go b/spanner/spansql/sql_test.go index d89b84aa02f1..0d2d961b7912 100644 --- a/spanner/spansql/sql_test.go +++ b/spanner/spansql/sql_test.go @@ -543,6 +543,28 @@ ORDER BY o.total DESC, u.name LIMIT 50 OFFSET 0`, reparseDDL, }, + { + &CreateView{ + Name: "ViewWithDistinct", + OrReplace: false, + SecurityType: Invoker, + Query: Query{ + Select: Select{ + Distinct: true, + List: []Expr{ID("city"), ID("country")}, + From: []SelectFrom{SelectFromTable{ + Table: "customers", + }}, + }, + }, + Position: line(1), + }, + `CREATE VIEW ViewWithDistinct SQL SECURITY INVOKER AS SELECT DISTINCT + city, + country +FROM customers`, + reparseDDL, + }, { &DropView{ Name: "SingersView", @@ -1510,7 +1532,13 @@ LIMIT 50 OFFSET 0`, Order: []Order{{Expr: ID("OCol"), Desc: true}}, Limit: IntegerLiteral(1000), }, - `SELECT A, B AS banana FROM Table WHERE C < "whelp" AND D IS NOT NULL ORDER BY OCol DESC LIMIT 1000`, + `SELECT + A, + B AS banana +FROM Table +WHERE C < "whelp" AND D IS NOT NULL +ORDER BY OCol DESC +LIMIT 1000`, reparseQuery, }, { @@ -1539,7 +1567,12 @@ LIMIT 50 OFFSET 0`, }, }, }, - `SELECT * FROM A WHERE NOT EXISTS (SELECT * FROM B)`, + `SELECT + * +FROM A +WHERE NOT EXISTS (SELECT + * +FROM B)`, reparseQuery, }, { @@ -1557,7 +1590,10 @@ LIMIT 50 OFFSET 0`, }, }, }, - `SELECT A FROM Table@{FORCE_INDEX=Idx} WHERE B = @b`, + `SELECT + A +FROM Table@{FORCE_INDEX=Idx} +WHERE B = @b`, reparseQuery, }, { @@ -1575,7 +1611,10 @@ LIMIT 50 OFFSET 0`, }, }, }, - `SELECT A FROM Table@{FORCE_INDEX=Idx,GROUPBY_SCAN_OPTIMIZATION=TRUE} WHERE B = @b`, + `SELECT + A +FROM Table@{FORCE_INDEX=Idx,GROUPBY_SCAN_OPTIMIZATION=TRUE} +WHERE B = @b`, reparseQuery, }, { @@ -1584,7 +1623,8 @@ LIMIT 50 OFFSET 0`, List: []Expr{IntegerLiteral(7)}, }, }, - `SELECT 7`, + `SELECT + 7`, reparseQuery, }, { @@ -1596,7 +1636,8 @@ LIMIT 50 OFFSET 0`, }}, }, }, - `SELECT CAST(7 AS STRING)`, + `SELECT + CAST(7 AS STRING)`, reparseQuery, }, { @@ -1608,7 +1649,8 @@ LIMIT 50 OFFSET 0`, }}, }, }, - `SELECT CAST(7 AS ENUM)`, + `SELECT + CAST(7 AS ENUM)`, reparseQuery, }, { @@ -1620,7 +1662,8 @@ LIMIT 50 OFFSET 0`, }}, }, }, - `SELECT SAFE_CAST(7 AS DATE)`, + `SELECT + SAFE_CAST(7 AS DATE)`, reparseQuery, }, { @@ -1661,7 +1704,7 @@ LIMIT 50 OFFSET 0`, }, }, }, - "SELECT `Desc`", + "SELECT\n\t`Desc`", reparseQuery, }, { @@ -1699,7 +1742,10 @@ LIMIT 50 OFFSET 0`, }, }, }, - `SELECT A, B FROM Table1 + `SELECT + A, + B +FROM Table1 INNER JOIN Table2 ON Table1.A = Table2.A`, reparseQuery, }, @@ -1728,7 +1774,10 @@ INNER JOIN Table2 ON Table1.A = Table2.A`, }, }, }, - `SELECT A, B FROM Table1 + `SELECT + A, + B +FROM Table1 INNER JOIN Table2 ON Table1.A = Table2.A INNER JOIN Table3 USING (X)`, reparseQuery, @@ -1748,7 +1797,8 @@ INNER JOIN Table3 USING (X)`, }, }, }, - `SELECT CASE X WHEN 1 THEN "X" WHEN 2 THEN "Y" ELSE NULL END`, + `SELECT + CASE X WHEN 1 THEN "X" WHEN 2 THEN "Y" ELSE NULL END`, reparseQuery, }, { @@ -1764,7 +1814,8 @@ INNER JOIN Table3 USING (X)`, }, }, }, - `SELECT CASE WHEN TRUE THEN "X" WHEN FALSE THEN "Y" END`, + `SELECT + CASE WHEN TRUE THEN "X" WHEN FALSE THEN "Y" END`, reparseQuery, }, { @@ -1779,7 +1830,8 @@ INNER JOIN Table3 USING (X)`, }, }, }, - `SELECT IF(1 < 2, TRUE, FALSE)`, + `SELECT + IF(1 < 2, TRUE, FALSE)`, reparseQuery, }, { @@ -1793,7 +1845,8 @@ INNER JOIN Table3 USING (X)`, }, }, }, - `SELECT IFNULL(10, 0)`, + `SELECT + IFNULL(10, 0)`, reparseQuery, }, { @@ -1807,7 +1860,8 @@ INNER JOIN Table3 USING (X)`, }, }, }, - `SELECT NULLIF(10, 0)`, + `SELECT + NULLIF(10, 0)`, reparseQuery, }, { @@ -1824,7 +1878,77 @@ INNER JOIN Table3 USING (X)`, }, }, }, - `SELECT COALESCE("A", NULL, "C")`, + `SELECT + COALESCE("A", NULL, "C")`, + reparseQuery, + }, + { + Query{ + Select: Select{ + Distinct: true, + List: []Expr{ID("city"), ID("country")}, + From: []SelectFrom{SelectFromTable{ + Table: "customers", + }}, + }, + }, + `SELECT DISTINCT + city, + country +FROM customers`, + reparseQuery, + }, + { + Query{ + Select: Select{ + List: []Expr{ + ID("department"), + Func{Name: "COUNT", Args: []Expr{Star}}, + }, + From: []SelectFrom{SelectFromTable{ + Table: "employees", + }}, + GroupBy: []Expr{ID("department")}, + }, + }, + `SELECT + department, + COUNT(*) +FROM employees +GROUP BY department`, + reparseQuery, + }, + { + Query{ + Select: Select{ + Distinct: true, + List: []Expr{ + ID("region"), + Func{Name: "AVG", Args: []Expr{ID("salary")}}, + }, + From: []SelectFrom{SelectFromTable{ + Table: "employees", + }}, + Where: ComparisonOp{ + Op: Gt, + LHS: ID("salary"), + RHS: IntegerLiteral(50000), + }, + GroupBy: []Expr{ID("region")}, + }, + Order: []Order{ + {Expr: ID("region")}, + }, + Limit: IntegerLiteral(100), + }, + `SELECT DISTINCT + region, + AVG(salary) +FROM employees +WHERE salary > 50000 +GROUP BY region +ORDER BY region +LIMIT 100`, reparseQuery, }, }