// Copyright 2018 Huan Du. All rights reserved. // Licensed under the MIT license that can be found in the LICENSE file. package sqlbuilder import ( "database/sql" "fmt" "testing" "github.com/huandu/go-assert" ) func ExampleSelect() { // Build a SQL to create a HIVE table using MySQL-like SQL syntax. sql, args := Select("columns[0] id", "columns[1] name", "columns[2] year"). From(MySQL.Quote("all-users.csv")). Limit(100). Build() fmt.Println(sql) fmt.Println(args) // Output: // SELECT columns[0] id, columns[1] name, columns[2] year FROM `all-users.csv` LIMIT ? // [100] } func ExampleSelectBuilder() { sb := NewSelectBuilder() sb.Distinct().Select("id", "name", sb.As("COUNT(*)", "t")) sb.From("demo.user") sb.Where( sb.GreaterThan("id", 1234), sb.Like("name", "%Du"), sb.Or( sb.IsNull("id_card"), sb.In("status", 1, 2, 5), ), sb.NotIn( "id", NewSelectBuilder().Select("id").From("banned"), ), // Nested SELECT. "modified_at > created_at + "+sb.Var(86400), // It's allowed to write arbitrary SQL. ) sb.GroupBy("status").Having(sb.NotIn("status", 4, 5)) sb.OrderByAsc("modified_at") sb.Limit(10).Offset(5) s, args := sb.Build() fmt.Println(s) fmt.Println(args) // Output: // SELECT DISTINCT id, name, COUNT(*) AS t FROM demo.user WHERE id > ? AND name LIKE ? AND (id_card IS NULL OR status IN (?, ?, ?)) AND id NOT IN (SELECT id FROM banned) AND modified_at > created_at + ? GROUP BY status HAVING status NOT IN (?, ?) ORDER BY modified_at ASC LIMIT ? OFFSET ? // [1234 %Du 1 2 5 86400 4 5 10 5] } func ExampleSelectBuilder_advancedUsage() { sb := NewSelectBuilder() innerSb := NewSelectBuilder() // Named arguments are supported. start := sql.Named("start", 1234567890) end := sql.Named("end", 1234599999) level := sql.Named("level", 20) sb.Select("id", "name") sb.From( sb.BuilderAs(innerSb, "user"), ) sb.Where( sb.In("status", Flatten([]int{1, 2, 3})...), sb.Between("created_at", start, end), ) sb.OrderByDesc("modified_at") innerSb.Select("*") innerSb.From("banned") innerSb.Where( innerSb.GreaterThan("level", level), innerSb.LessEqualThan("updated_at", end), innerSb.NotIn("name", Flatten([]string{"Huan Du", "Charmy Liu"})...), ) s, args := sb.Build() fmt.Println(s) fmt.Println(args) // Output: // SELECT id, name FROM (SELECT * FROM banned WHERE level > @level AND updated_at <= @end AND name NOT IN (?, ?)) AS user WHERE status IN (?, ?, ?) AND created_at BETWEEN @start AND @end ORDER BY modified_at DESC // [Huan Du Charmy Liu 1 2 3 {{} level 20} {{} end 1234599999} {{} start 1234567890}] } func ExampleSelectBuilder_join() { sb := NewSelectBuilder() sb.Select("u.id", "u.name", "c.type", "p.nickname") sb.From("user u") sb.Join("contract c", "u.id = c.user_id", sb.In("c.status", 1, 2, 5), ) sb.JoinWithOption(RightOuterJoin, "person p", "u.id = p.user_id", sb.Like("p.surname", "%Du"), ) sb.Where( "u.modified_at > u.created_at + " + sb.Var(86400), // It's allowed to write arbitrary SQL. ) sql, args := sb.Build() fmt.Println(sql) fmt.Println(args) // Output: // SELECT u.id, u.name, c.type, p.nickname FROM user u JOIN contract c ON u.id = c.user_id AND c.status IN (?, ?, ?) RIGHT OUTER JOIN person p ON u.id = p.user_id AND p.surname LIKE ? WHERE u.modified_at > u.created_at + ? // [1 2 5 %Du 86400] } func ExampleSelectBuilder_nestedJoin() { sb := NewSelectBuilder() nestedSb := NewSelectBuilder() // Build the nested subquery nestedSb.Select("b.id", "b.user_id") nestedSb.From("users2 AS b") nestedSb.Where(nestedSb.GreaterThan("b.age", 20)) // Build the main query with nested join sb.Select("a.id", "a.user_id") sb.From("users AS a") sb.Join( sb.BuilderAs(nestedSb, "b"), "a.user_id = b.user_id", ) sql, args := sb.Build() fmt.Println(sql) fmt.Println(args) // Output: // SELECT a.id, a.user_id FROM users AS a JOIN (SELECT b.id, b.user_id FROM users2 AS b WHERE b.age > ?) AS b ON a.user_id = b.user_id // [20] } func ExampleSelectBuilder_limit_offset() { flavors := []Flavor{MySQL, PostgreSQL, SQLite, SQLServer, CQL, Datastore, Presto, Oracle, Informix, Doris} results := make([][]string, len(flavors)) sb := NewSelectBuilder() saveResults := func() { for i, f := range flavors { s, _ := sb.BuildWithFlavor(f) results[i] = append(results[i], s) } } sb.Select("*") sb.From("user") // Case #1: limit < 0 and offset < 0 // // All: No limit or offset in query. sb.Limit(-1) sb.Offset(-1) saveResults() // Case #2: limit < 0 and offset >= 0 // // MySQL and SQLite: Ignore offset if the limit is not set. // PostgreSQL: Offset can be set without limit. // SQLServer: Offset can be set without limit. // CQL: Ignore offset. // Oracle: Offset can be set without limit. sb.Limit(-1) sb.Offset(0) saveResults() // Case #3: limit >= 0 and offset >= 0 // // CQL: Ignore offset. // All others: Set both limit and offset. sb.Limit(1) sb.Offset(0) saveResults() // Case #4: limit >= 0 and offset < 0 // // All: Set limit in query. sb.Limit(1) sb.Offset(-1) saveResults() // Case #5: limit >= 0 and offset >= 0 order by id // // CQL: Ignore offset. // All others: Set both limit and offset. sb.Limit(1) sb.Offset(1) sb.OrderBy("id") saveResults() for i, result := range results { fmt.Println() fmt.Println(flavors[i]) for n, s := range result { fmt.Printf("#%d: %s\n", n+1, s) } } // Output: // // MySQL // #1: SELECT * FROM user // #2: SELECT * FROM user // #3: SELECT * FROM user LIMIT ? OFFSET ? // #4: SELECT * FROM user LIMIT ? // #5: SELECT * FROM user ORDER BY id LIMIT ? OFFSET ? // // PostgreSQL // #1: SELECT * FROM user // #2: SELECT * FROM user OFFSET $1 // #3: SELECT * FROM user LIMIT $1 OFFSET $2 // #4: SELECT * FROM user LIMIT $1 // #5: SELECT * FROM user ORDER BY id LIMIT $1 OFFSET $2 // // SQLite // #1: SELECT * FROM user // #2: SELECT * FROM user // #3: SELECT * FROM user LIMIT ? OFFSET ? // #4: SELECT * FROM user LIMIT ? // #5: SELECT * FROM user ORDER BY id LIMIT ? OFFSET ? // // SQLServer // #1: SELECT * FROM user // #2: SELECT * FROM user ORDER BY 1 OFFSET @p1 ROWS // #3: SELECT * FROM user ORDER BY 1 OFFSET @p1 ROWS FETCH NEXT @p2 ROWS ONLY // #4: SELECT * FROM user ORDER BY 1 OFFSET 0 ROWS FETCH NEXT @p1 ROWS ONLY // #5: SELECT * FROM user ORDER BY id OFFSET @p1 ROWS FETCH NEXT @p2 ROWS ONLY // // CQL // #1: SELECT * FROM user // #2: SELECT * FROM user // #3: SELECT * FROM user LIMIT ? // #4: SELECT * FROM user LIMIT ? // #5: SELECT * FROM user ORDER BY id LIMIT ? // // Datastore // #1: SELECT * FROM user // #2: SELECT * FROM user // #3: SELECT * FROM user LIMIT ? OFFSET ? // #4: SELECT * FROM user LIMIT ? // #5: SELECT * FROM user ORDER BY id LIMIT ? OFFSET ? // // Presto // #1: SELECT * FROM user // #2: SELECT * FROM user OFFSET ? // #3: SELECT * FROM user OFFSET ? LIMIT ? // #4: SELECT * FROM user LIMIT ? // #5: SELECT * FROM user ORDER BY id OFFSET ? LIMIT ? // // Oracle // #1: SELECT * FROM user // #2: SELECT * FROM user OFFSET :1 ROWS // #3: SELECT * FROM user OFFSET :1 ROWS FETCH NEXT :2 ROWS ONLY // #4: SELECT * FROM user OFFSET 0 ROWS FETCH NEXT :1 ROWS ONLY // #5: SELECT * FROM user ORDER BY id OFFSET :1 ROWS FETCH NEXT :2 ROWS ONLY // // Informix // #1: SELECT * FROM user // #2: SELECT * FROM user // #3: SELECT * FROM user SKIP ? FIRST ? // #4: SELECT * FROM user FIRST ? // #5: SELECT * FROM user ORDER BY id SKIP ? FIRST ? // // Doris // #1: SELECT * FROM user // #2: SELECT * FROM user // #3: SELECT * FROM user LIMIT 1 OFFSET 0 // #4: SELECT * FROM user LIMIT 1 // #5: SELECT * FROM user ORDER BY id LIMIT 1 OFFSET 1 } func ExampleSelectBuilder_ForUpdate() { sb := newSelectBuilder() sb.Select("*").From("user").Where( sb.Equal("id", 1234), ).ForUpdate() sql, args := sb.Build() fmt.Println(sql) fmt.Println(args) // Output: // SELECT * FROM user WHERE id = ? FOR UPDATE // [1234] } func ExampleSelectBuilder_varInCols() { // Column name may contain some characters, e.g. the $ sign, which have special meanings in builders. // It's recommended to call Escape() or EscapeAll() to escape the name. sb := NewSelectBuilder() v := sb.Var("foo") sb.Select(Escape("colHasA$Sign"), v) sb.From("table") s, args := sb.Build() fmt.Println(s) fmt.Println(args) // Output: // SELECT colHasA$Sign, ? FROM table // [foo] } func ExampleSelectBuilder_SQL() { sb := NewSelectBuilder() sb.SQL("/* before */") sb.Select("u.id", "u.name", "c.type", "p.nickname") sb.SQL("/* after select */") sb.From("user u") sb.SQL("/* after from */") sb.Join("contract c", "u.id = c.user_id", ) sb.JoinWithOption(RightOuterJoin, "person p", "u.id = p.user_id", ) sb.SQL("/* after join */") sb.Where( "u.modified_at > u.created_at", ) sb.SQL("/* after where */") sb.OrderBy("id") sb.SQL("/* after order by */") sb.Limit(10) sb.SQL("/* after limit */") sb.ForShare() sb.SQL("/* after for */") s := sb.String() fmt.Println(s) // Output: // /* before */ SELECT u.id, u.name, c.type, p.nickname /* after select */ FROM user u /* after from */ JOIN contract c ON u.id = c.user_id RIGHT OUTER JOIN person p ON u.id = p.user_id /* after join */ WHERE u.modified_at > u.created_at /* after where */ ORDER BY id /* after order by */ LIMIT ? /* after limit */ FOR SHARE /* after for */ } // Example for issue #115. func ExampleSelectBuilder_customSELECT() { sb := NewSelectBuilder() // Set a custom SELECT clause. sb.SQL("SELECT id, name FROM user").Where( sb.In("id", 1, 2, 3), ) s, args := sb.Build() fmt.Println(s) fmt.Println(args) // Output: // SELECT id, name FROM user WHERE id IN (?, ?, ?) // [1 2 3] } func ExampleSelectBuilder_NumCol() { sb := NewSelectBuilder() sb.Select("id", "name", "created_at") sb.From("demo.user") sb.Where( sb.GreaterThan("id", 1234), ) // Count the number of columns. fmt.Println(sb.NumCol()) // Output: // 3 } func ExampleSelectBuilder_With() { sql := With( CTEQuery("users").As( Select("id", "name").From("users").Where("prime IS NOT NULL"), ), // The CTE table orders will be added to table list of FROM clause automatically. CTETable("orders").As( Select("id", "user_id").From("orders"), ), ).Select("orders.id").Join("users", "orders.user_id = users.id").Limit(10).String() fmt.Println(sql) // Output: // WITH users AS (SELECT id, name FROM users WHERE prime IS NOT NULL), orders AS (SELECT id, user_id FROM orders) SELECT orders.id FROM orders JOIN users ON orders.user_id = users.id LIMIT ? } func TestSelectBuilderSelectMore(t *testing.T) { a := assert.New(t) sb := Select("id").SQL("/* first */").Where( "name IS NOT NULL", ).SQL("/* second */").SelectMore("name").SQL("/* third */") a.Equal(sb.String(), "SELECT id, name /* first */ /* third */ WHERE name IS NOT NULL /* second */") } func TestSelectBuilderGetFlavor(t *testing.T) { a := assert.New(t) sb := newSelectBuilder() sb.SetFlavor(PostgreSQL) flavor := sb.Flavor() a.Equal(PostgreSQL, flavor) sbClick := Datastore.NewSelectBuilder() flavor = sbClick.Flavor() a.Equal(Datastore, flavor) } func ExampleSelectBuilder_LateralAs() { // Demo SQL comes from a sample on https://dev.mysql.com/doc/refman/8.4/en/lateral-derived-tables.html. sb := Select( "salesperson.name", "max_sale.amount", "max_sale.customer_name", ) sb.From( "salesperson", sb.LateralAs( Select("amount", "customer_name"). From("all_sales"). Where( "all_sales.salesperson_id = salesperson.id", ). OrderByDesc("amount").Limit(1), "max_sale", ), ) fmt.Println(sb) // Output: // SELECT salesperson.name, max_sale.amount, max_sale.customer_name FROM salesperson, LATERAL (SELECT amount, customer_name FROM all_sales WHERE all_sales.salesperson_id = salesperson.id ORDER BY amount DESC LIMIT ?) AS max_sale } func TestNilPointerWhere(t *testing.T) { NewSelectBuilder().SQL("$0").Build() NewSelectBuilder().SQL("$0").BuildWithFlavor(DefaultFlavor) } func TestSelectBuilderClone(t *testing.T) { a := assert.New(t) cte := With( CTETable("users").As( Select("id", "name").From("users").Where("name IS NOT NULL"), ), ) sb := cte.Select("users.id", "orders.id").From("orders").Where( "users.id = orders.user_id", ).OrderBy("orders.id").Desc().Limit(10).Offset(2) // Clone and compare clone := sb.Clone() s1, args1 := sb.Build() s2, args2 := clone.Build() a.Equal(s1, s2) a.Equal(args1, args2) // Mutate clone and ensure original unchanged clone.Limit(100).Asc() s1After := sb.String() s2After := clone.String() a.NotEqual(s1After, s2After) } func ExampleSelectBuilder_OrderByAsc() { sb := NewSelectBuilder() sb.Select("id", "name", "score") sb.From("users") sb.Where(sb.GreaterThan("score", 0)) sb.OrderByAsc("name") sql, args := sb.Build() fmt.Println(sql) fmt.Println(args) // Output: // SELECT id, name, score FROM users WHERE score > ? ORDER BY name ASC // [0] } func ExampleSelectBuilder_OrderByDesc() { sb := NewSelectBuilder() sb.Select("id", "name", "score") sb.From("users") sb.Where(sb.GreaterThan("score", 0)) sb.OrderByDesc("score") sql, args := sb.Build() fmt.Println(sql) fmt.Println(args) // Output: // SELECT id, name, score FROM users WHERE score > ? ORDER BY score DESC // [0] } func ExampleSelectBuilder_OrderByAsc_multiple() { sb := NewSelectBuilder() sb.Select("id", "name", "score") sb.From("users") sb.Where(sb.GreaterThan("score", 0)) // Chain multiple OrderByAsc and OrderByDesc calls with different directions sb.OrderByDesc("score").OrderByAsc("name").OrderByDesc("id") sql, args := sb.Build() fmt.Println(sql) fmt.Println(args) // Output: // SELECT id, name, score FROM users WHERE score > ? ORDER BY score DESC, name ASC, id DESC // [0] } func TestSelectBuilder_OrderByAscDesc(t *testing.T) { a := assert.New(t) // Test OrderByAsc with single column sb := NewSelectBuilder() sb.Select("*").From("users").OrderByAsc("name") sql, _ := sb.Build() a.Equal("SELECT * FROM users ORDER BY name ASC", sql) // Test OrderByDesc with single column sb = NewSelectBuilder() sb.Select("*").From("users").OrderByDesc("id") sql, _ = sb.Build() a.Equal("SELECT * FROM users ORDER BY id DESC", sql) // Test chaining OrderByAsc and OrderByDesc sb = NewSelectBuilder() sb.Select("*").From("users") sb.OrderByDesc("score").OrderByAsc("name") sql, _ = sb.Build() a.Equal("SELECT * FROM users ORDER BY score DESC, name ASC", sql) // Test multiple OrderByDesc calls sb = NewSelectBuilder() sb.Select("*").From("users") sb.OrderByDesc("score").OrderByDesc("id") sql, _ = sb.Build() a.Equal("SELECT * FROM users ORDER BY score DESC, id DESC", sql) // Test multiple OrderByAsc calls sb = NewSelectBuilder() sb.Select("*").From("users") sb.OrderByAsc("name").OrderByAsc("email") sql, _ = sb.Build() a.Equal("SELECT * FROM users ORDER BY name ASC, email ASC", sql) // Test mixed ordering with more complex scenario sb = NewSelectBuilder() sb.Select("id", "name", "score", "created_at").From("users") sb.Where(sb.GreaterThan("score", 0)) sb.OrderByDesc("score").OrderByAsc("name").OrderByDesc("created_at") sql, args := sb.Build() a.Equal("SELECT id, name, score, created_at FROM users WHERE score > ? ORDER BY score DESC, name ASC, created_at DESC", sql) a.Equal([]interface{}{0}, args) // Test that OrderByAsc/OrderByDesc work with table aliases sb = NewSelectBuilder() sb.Select("u.id", "u.name", "o.total").From("users u") sb.Join("orders o", "u.id = o.user_id") sb.OrderByDesc("o.total").OrderByAsc("u.name") sql, _ = sb.Build() a.Equal("SELECT u.id, u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id ORDER BY o.total DESC, u.name ASC", sql) }