mirror of
https://github.com/hanzoai/sqlbuilder.git
synced 2026-10-01 00:37:42 +00:00
- go.mod module path -> github.com/hanzoai/sqlbuilder (package name kept: sqlbuilder) - Rename exported Flavor constant ClickHouse -> Datastore (same iota ordinal 6, behavior byte-identical): const decl, all case labels (Interpolate/Quote/ PrepareInsertIgnore/args/select/union), Flavor.String() literal "ClickHouse" -> "Datastore", and the name<->flavor test map. - Rename unexported dialect helper clickhouseInterpolate -> datastoreInterpolate. - Rename example ExampleInsertBuilder_insertIgnore_clickhouse -> _datastore. - Update tests + README flavor list to Datastore. - Keep upstream MIT LICENSE; add NOTICE recording the fork + rename. Dialect SQL output unchanged; only the identifier is renamed.
581 lines
15 KiB
Go
581 lines
15 KiB
Go
// 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)
|
|
}
|