Skip to content

Commit

Permalink
add doc and freestyle builder
Browse files Browse the repository at this point in the history
  • Loading branch information
huandu committed Dec 28, 2017
1 parent 59e26de commit b475234
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 0 deletions.
98 changes: 98 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,111 @@ Use `go get` to install this package.

## Usage ##

### Basic usage ###

Here is a sample to demonstrate how to build a SELECT query.

```go
sb := sqlbuilder.NewSelectBuilder()

sb.Select("id", "name", sb.As("COUNT(*)", c))
sb.From("user")
sb.Where(sb.In("status", 1, 2, 5))

sql, args := sb.Build()
fmt.Println(sql)
fmt.Println(args)

// Output:
// SELECT id, name, COUNT(*) AS c FROM user WHERE status IN (?, ?, ?)
// [1 2 5]
```

Following builders are implemented right now. API document and examples are provided in the `godoc` document.

* [SelectBuilder](https://godoc.org/github.com/huandu/go-sqlbuilder#SelectBuilder)
* [InsertBuilder](https://godoc.org/github.com/huandu/go-sqlbuilder#InsertBuilder)
* [UpdateBuilder](https://godoc.org/github.com/huandu/go-sqlbuilder#UpdateBuilder)
* [DeleteBuilder](https://godoc.org/github.com/huandu/go-sqlbuilder#DeleteBuilder)

### Nested SQL ###

It's quite straight forward to create a nested SQL: use a builder as an argument to nest it.

Here is a simple sample.

```go
sb := sqlbuilder.NewSelectBuilder()
fromSb := sqlbuilder.NewSelectBuilder()
statusSb := sqlbuilder.NewSelectBuilder()

sb.Select("id")
sb.From(sb.As(fmt.Sprintf("(%v)", sb.Var(fromSb), "user")))
sb.Where(sb.In("status", statusSb))

fromSb.Select("id")
fromSb.From("user")
fromSb.Where(sb.G("level", 4))

statusSb.Select("status")
statusSb.From("config")
statusSb.Where(sb.E("state", 1))

sql, args := sb.Build()
fmt.Println(sql)
fmt.Println(args)

// Output:
// SELECT id FROM (SELECT id FROM user WHERE level > ?) AS user WHERE status IN (SELECT status FROM config WHERE state = ?)
// [4 1]
```

### Use `sql.Named` in a builder ###

The function `sql.Named` defined in `database/sql` can create a named argument in SQL. It's necessary if we want to reuse an argument several times in one SQL. It's still quite simple to use named arguments in a builder: use it as an argument.

Here is a sample.

```go
now := time.Now().Unix()
start := sql.Named("start", now-86400)
end := sql.Named("end", now+86400)
sb := sqlbuilder.NewSelectBuilder()

sb.Select("name")
sb.From("user")
sb.Where(
sb.Between("created_at", start, end),
sb.GE("modified_at", start),
)

sql, args := sb.Build()
fmt.Println(sql)
fmt.Println(args)

// Output:
// SELECT name FROM user WHERE created_at BETWEEN @start AND @end AND modified_at >= @start
// [{{} start 1514458225} {{} end 1514544625}]
```

### Freestyle builder ###

A builder is only a way to record arguments. If we want to build a long SQL with lots of special syntax (e.g. special comments for a database proxy), simply use `Buildf` to format a SQL string.

```go
sb := sqlbuilder.NewSelectBuilder()
sb.Select("id").From("user")

explain := sqlbuilder.Buildf("EXPLAIN %v LEFT JOIN SELECT * FROM banned WHERE state = (%v, %v)", sb, 1, 2)
sql, args := explain.Build()
fmt.Println(sql)
fmt.Println(args)

// Output:
// EXPLAIN SELECT id FROM user LEFT JOIN SELECT * FROM banned WHERE state = (?, ?)
// [1 2]
```

## License ##

This package is licensed under MIT license. See LICENSE for details.
28 changes: 28 additions & 0 deletions builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,37 @@

package sqlbuilder

import (
"fmt"
)

// Builder is a general SQL builder.
// It's used by Args to create nested SQL like the `IN` expression in
// `SELECT * FROM t1 WHERE id IN (SELECT id FROM t2)`.
type Builder interface {
Build() (sql string, args []interface{})
}

type freestyleBuilder struct {
sql string
args *Args
}

func (fb *freestyleBuilder) Build() (sql string, args []interface{}) {
return fb.args.Compile(fb.sql)
}

// Buildf creates a Builder from a fmt string.
func Buildf(format string, arg ...interface{}) Builder {
args := &Args{}
vars := make([]interface{}, 0, len(arg))

for _, a := range arg {
vars = append(vars, args.Add(a))
}

return &freestyleBuilder{
sql: fmt.Sprintf(format, vars...),
args: args,
}
}
22 changes: 22 additions & 0 deletions builder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2018 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.

package sqlbuilder

import (
"fmt"
)

func ExampleBuildf() {
sb := NewSelectBuilder()
sb.Select("id").From("user")

explain := Buildf("EXPLAIN %v LEFT JOIN SELECT * FROM banned WHERE state = (%v, %v)", sb, 1, 2)
sql, args := explain.Build()
fmt.Println(sql)
fmt.Println(args)

// Output:
// EXPLAIN SELECT id FROM user LEFT JOIN SELECT * FROM banned WHERE state = (?, ?)
// [1 2]
}
5 changes: 5 additions & 0 deletions cond.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ func (c *Cond) Between(field string, lower, upper interface{}) string {
return fmt.Sprintf("%v BETWEEN %v AND %v", Escape(field), c.Args.Add(lower), c.Args.Add(upper))
}

// NotBetween represents "field NOT BETWEEN lower AND upper".
func (c *Cond) NotBetween(field string, lower, upper interface{}) string {
return fmt.Sprintf("%v NOT BETWEEN %v AND %v", Escape(field), c.Args.Add(lower), c.Args.Add(upper))
}

// Or represents OR logic like "expr1 OR expr2 OR expr3".
func (c *Cond) Or(orExpr ...string) string {
return fmt.Sprintf("(%v)", strings.Join(orExpr, " OR "))
Expand Down
1 change: 1 addition & 0 deletions cond_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func TestCond(t *testing.T) {
"$$a IS NULL": func() string { return newTestCond().IsNull("$a") },
"$$a IS NOT NULL": func() string { return newTestCond().IsNotNull("$a") },
"$$a BETWEEN $0 AND $1": func() string { return newTestCond().Between("$a", 123, 456) },
"$$a NOT BETWEEN $0 AND $1": func() string { return newTestCond().NotBetween("$a", 123, 456) },
"(1 = 1 OR 2 = 2 OR 3 = 3)": func() string { return newTestCond().Or("1 = 1", "2 = 2", "3 = 3") },
"$0": func() string { return newTestCond().Var(123) },
}
Expand Down

0 comments on commit b475234

Please sign in to comment.