refs #48 #49 #53: add new method SQL in all builders

All builders add a new method called SQL to enable us to add any
arbitrary SQL when building. It's quite useful when we are using some
kinds of extension syntax or add special comments in SQL.
This commit is contained in:
Huan Du
2021-02-04 00:07:55 +08:00
parent 041d144b16
commit b15ca56b4d
15 changed files with 794 additions and 54 deletions
+39 -1
View File
@@ -19,7 +19,20 @@ go get github.com/huandu/go-sqlbuilder
### Basic usage
Here is a sample to demonstrate how to build a SELECT query.
We can build a SQL really quick with this package.
```go
sql := sqlbuilder.Select("id", "name").From("demo.user").
Where("status = 1").Limit(10).
String()
fmt.Println(sql)
// Output:
// SELECT id, name FROM demo.user WHERE status = 1 LIMIT 10
```
In the most cases, we need to escape all input from user. In this case, create a builder before starting.
```go
sb := sqlbuilder.NewSelectBuilder()
@@ -37,6 +50,8 @@ fmt.Println(args)
// [1 2 5]
```
### Pre-defined SQL builders
Following builders are implemented right now. API document and examples are provided in the `godoc` document.
- [Struct](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Struct): Builder factory for a struct.
@@ -50,6 +65,29 @@ Following builders are implemented right now. API document and examples are prov
- [Build](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Build): Advanced freestyle builder using special syntax defined in [Args#Compile](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#Args.Compile).
- [BuildNamed](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#BuildNamed): Advanced freestyle builder using `${key}` to refer the value of a map by key.
There is a method `SQL(sql string)` implemented by all statement builders like `SelectBuilder`. We can use this method to insert any arbitrary SQL fragment when building a SQL. It's quite useful to build SQL containing non-standard syntax supported by a OLTP or OLAP system.
```go
// Build a SQL to create a HIVE table.
sql := sqlbuilder.CreateTable("users").
SQL("PARTITION BY (year)").
SQL("AS").
SQL(
sqlbuilder.Select("columns[0] id", "columns[1] name", "columns[2] year").
From("`all-users.csv`").
Limit(100).
String(),
).
String()
fmt.Println(sql)
// Output:
// CREATE TABLE users PARTITION BY (year) AS SELECT columns[0] id, columns[1] name, columns[2] year FROM `all-users.csv` LIMIT 100
```
To learn how to use builders, check out [examples on GoDoc](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#pkg-examples).
### Build SQL for MySQL, PostgreSQL or SQLite
Parameter markers are different in MySQL, PostgreSQL and SQLite. This package provides some methods to set the type of markers (we call it "flavor") in all builders.
+51 -9
View File
@@ -8,6 +8,13 @@ import (
"strings"
)
const (
createTableMarkerInit injectionMarker = iota
createTableMarkerAfterCreate
createTableMarkerAfterDefine
createTableMarkerAfterOption
)
// NewCreateTableBuilder creates a new CREATE TABLE builder.
func NewCreateTableBuilder() *CreateTableBuilder {
return DefaultFlavor.NewCreateTableBuilder()
@@ -16,8 +23,10 @@ func NewCreateTableBuilder() *CreateTableBuilder {
func newCreateTableBuilder() *CreateTableBuilder {
args := &Args{}
return &CreateTableBuilder{
verb: "CREATE TABLE",
args: args,
verb: "CREATE TABLE",
args: args,
injection: newInjection(),
marker: createTableMarkerInit,
}
}
@@ -30,11 +39,22 @@ type CreateTableBuilder struct {
options [][]string
args *Args
injection *injection
marker injectionMarker
}
var _ Builder = new(CreateTableBuilder)
// CreateTable sets the table name in CREATE TABLE.
func CreateTable(table string) *CreateTableBuilder {
return DefaultFlavor.NewCreateTableBuilder().CreateTable(table)
}
// CreateTable sets the table name in CREATE TABLE.
func (ctb *CreateTableBuilder) CreateTable(table string) *CreateTableBuilder {
ctb.table = Escape(table)
ctb.marker = createTableMarkerAfterCreate
return ctb
}
@@ -42,6 +62,7 @@ func (ctb *CreateTableBuilder) CreateTable(table string) *CreateTableBuilder {
func (ctb *CreateTableBuilder) CreateTempTable(table string) *CreateTableBuilder {
ctb.verb = "CREATE TEMPORARY TABLE"
ctb.table = Escape(table)
ctb.marker = createTableMarkerAfterCreate
return ctb
}
@@ -54,12 +75,14 @@ func (ctb *CreateTableBuilder) IfNotExists() *CreateTableBuilder {
// Define adds definition of a column or index in CREATE TABLE.
func (ctb *CreateTableBuilder) Define(def ...string) *CreateTableBuilder {
ctb.defs = append(ctb.defs, def)
ctb.marker = createTableMarkerAfterDefine
return ctb
}
// Option adds a table option in CREATE TABLE.
func (ctb *CreateTableBuilder) Option(opt ...string) *CreateTableBuilder {
ctb.options = append(ctb.options, opt)
ctb.marker = createTableMarkerAfterOption
return ctb
}
@@ -79,6 +102,7 @@ func (ctb *CreateTableBuilder) Build() (sql string, args []interface{}) {
// They can be used in `DB#Query` of package `database/sql` directly.
func (ctb *CreateTableBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{}) (sql string, args []interface{}) {
buf := &bytes.Buffer{}
ctb.injection.WriteTo(buf, createTableMarkerInit)
buf.WriteString(ctb.verb)
if ctb.ifNotExists {
@@ -87,17 +111,23 @@ func (ctb *CreateTableBuilder) BuildWithFlavor(flavor Flavor, initialArg ...inte
buf.WriteRune(' ')
buf.WriteString(ctb.table)
buf.WriteString(" (")
ctb.injection.WriteTo(buf, createTableMarkerAfterCreate)
defs := make([]string, 0, len(ctb.defs))
if len(ctb.defs) > 0 {
buf.WriteString(" (")
for _, def := range ctb.defs {
defs = append(defs, strings.Join(def, " "))
defs := make([]string, 0, len(ctb.defs))
for _, def := range ctb.defs {
defs = append(defs, strings.Join(def, " "))
}
buf.WriteString(strings.Join(defs, ", "))
buf.WriteRune(')')
ctb.injection.WriteTo(buf, createTableMarkerAfterDefine)
}
buf.WriteString(strings.Join(defs, ", "))
buf.WriteRune(')')
if len(ctb.options) > 0 {
buf.WriteRune(' ')
@@ -108,6 +138,7 @@ func (ctb *CreateTableBuilder) BuildWithFlavor(flavor Flavor, initialArg ...inte
}
buf.WriteString(strings.Join(opts, ", "))
ctb.injection.WriteTo(buf, createTableMarkerAfterOption)
}
return ctb.args.CompileWithFlavor(buf.String(), flavor, initialArg...)
@@ -119,3 +150,14 @@ func (ctb *CreateTableBuilder) SetFlavor(flavor Flavor) (old Flavor) {
ctb.args.Flavor = flavor
return
}
// Var returns a placeholder for value.
func (ctb *CreateTableBuilder) Var(arg interface{}) string {
return ctb.args.Add(arg)
}
// SQL adds an arbitrary sql to current position.
func (ctb *CreateTableBuilder) SQL(sql string) *CreateTableBuilder {
ctb.injection.SQL(ctb.marker, sql)
return ctb
}
+31
View File
@@ -7,6 +7,17 @@ import (
"fmt"
)
func ExampleCreateTable() {
sql := CreateTable("demo.user").IfNotExists().
Define("id", "BIGINT(20)", "NOT NULL", "AUTO_INCREMENT", "PRIMARY KEY", `COMMENT "user id"`).
String()
fmt.Println(sql)
// Output:
// CREATE TABLE IF NOT EXISTS demo.user (id BIGINT(20) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT "user id")
}
func ExampleCreateTableBuilder() {
ctb := NewCreateTableBuilder()
ctb.CreateTable("demo.user").IfNotExists()
@@ -38,3 +49,23 @@ func ExampleCreateTableBuilder_tempTable() {
// Output:
// CREATE TEMPORARY TABLE IF NOT EXISTS demo.user (id BIGINT(20) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT "user id", name VARCHAR(255) NOT NULL COMMENT "user name", created_at DATETIME NOT NULL COMMENT "user create time", modified_at DATETIME NOT NULL COMMENT "user modify time", KEY idx_name_modified_at name, modified_at) DEFAULT CHARACTER SET utf8mb4
}
func ExampleCreateTableBuilder_SQL() {
ctb := NewCreateTableBuilder()
ctb.SQL(`/* before */`)
ctb.CreateTempTable("demo.user").IfNotExists()
ctb.SQL("/* after create */")
ctb.Define("id", "BIGINT(20)", "NOT NULL", "AUTO_INCREMENT", "PRIMARY KEY", `COMMENT "user id"`)
ctb.Define("name", "VARCHAR(255)", "NOT NULL", `COMMENT "user name"`)
ctb.SQL("/* after define */")
ctb.Option("DEFAULT CHARACTER SET", "utf8mb4")
ctb.SQL(ctb.Var(Build("AS SELECT * FROM old.user WHERE name LIKE $?", "%Huan%")))
sql, args := ctb.Build()
fmt.Println(sql)
fmt.Println(args)
// Output:
// /* before */ CREATE TEMPORARY TABLE IF NOT EXISTS demo.user /* after create */ (id BIGINT(20) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT "user id", name VARCHAR(255) NOT NULL COMMENT "user name") /* after define */ DEFAULT CHARACTER SET utf8mb4 AS SELECT * FROM old.user WHERE name LIKE ?
// [%Huan%]
}
+84 -3
View File
@@ -5,9 +5,18 @@ package sqlbuilder
import (
"bytes"
"strconv"
"strings"
)
const (
deleteMarkerInit injectionMarker = iota
deleteMarkerAfterDeleteFrom
deleteMarkerAfterWhere
deleteMarkerAfterOrderBy
deleteMarkerAfterLimit
)
// NewDeleteBuilder creates a new DELETE builder.
func NewDeleteBuilder() *DeleteBuilder {
return DefaultFlavor.NewDeleteBuilder()
@@ -19,7 +28,9 @@ func newDeleteBuilder() *DeleteBuilder {
Cond: Cond{
Args: args,
},
args: args,
limit: -1,
args: args,
injection: newInjection(),
}
}
@@ -27,23 +38,64 @@ func newDeleteBuilder() *DeleteBuilder {
type DeleteBuilder struct {
Cond
table string
whereExprs []string
table string
whereExprs []string
orderByCols []string
order string
limit int
args *Args
injection *injection
marker injectionMarker
}
var _ Builder = new(DeleteBuilder)
// DeleteFrom sets table name in DELETE.
func DeleteFrom(table string) *DeleteBuilder {
return DefaultFlavor.NewDeleteBuilder().DeleteFrom(table)
}
// DeleteFrom sets table name in DELETE.
func (db *DeleteBuilder) DeleteFrom(table string) *DeleteBuilder {
db.table = Escape(table)
db.marker = deleteMarkerAfterDeleteFrom
return db
}
// Where sets expressions of WHERE in DELETE.
func (db *DeleteBuilder) Where(andExpr ...string) *DeleteBuilder {
db.whereExprs = append(db.whereExprs, andExpr...)
db.marker = deleteMarkerAfterWhere
return db
}
// OrderBy sets columns of ORDER BY in DELETE.
func (db *DeleteBuilder) OrderBy(col ...string) *DeleteBuilder {
db.orderByCols = col
db.marker = deleteMarkerAfterOrderBy
return db
}
// Asc sets order of ORDER BY to ASC.
func (db *DeleteBuilder) Asc() *DeleteBuilder {
db.order = "ASC"
db.marker = deleteMarkerAfterOrderBy
return db
}
// Desc sets order of ORDER BY to DESC.
func (db *DeleteBuilder) Desc() *DeleteBuilder {
db.order = "DESC"
db.marker = deleteMarkerAfterOrderBy
return db
}
// Limit sets the LIMIT in DELETE.
func (db *DeleteBuilder) Limit(limit int) *DeleteBuilder {
db.limit = limit
db.marker = deleteMarkerAfterLimit
return db
}
@@ -63,12 +115,35 @@ func (db *DeleteBuilder) Build() (sql string, args []interface{}) {
// They can be used in `DB#Query` of package `database/sql` directly.
func (db *DeleteBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{}) (sql string, args []interface{}) {
buf := &bytes.Buffer{}
db.injection.WriteTo(buf, deleteMarkerInit)
buf.WriteString("DELETE FROM ")
buf.WriteString(db.table)
db.injection.WriteTo(buf, deleteMarkerAfterDeleteFrom)
if len(db.whereExprs) > 0 {
buf.WriteString(" WHERE ")
buf.WriteString(strings.Join(db.whereExprs, " AND "))
db.injection.WriteTo(buf, deleteMarkerAfterWhere)
}
if len(db.orderByCols) > 0 {
buf.WriteString(" ORDER BY ")
buf.WriteString(strings.Join(db.orderByCols, ", "))
if db.order != "" {
buf.WriteRune(' ')
buf.WriteString(db.order)
}
db.injection.WriteTo(buf, deleteMarkerAfterOrderBy)
}
if db.limit >= 0 {
buf.WriteString(" LIMIT ")
buf.WriteString(strconv.Itoa(db.limit))
db.injection.WriteTo(buf, deleteMarkerAfterLimit)
}
return db.args.CompileWithFlavor(buf.String(), flavor, initialArg...)
@@ -80,3 +155,9 @@ func (db *DeleteBuilder) SetFlavor(flavor Flavor) (old Flavor) {
db.args.Flavor = flavor
return
}
// SQL adds an arbitrary sql to current position.
func (db *DeleteBuilder) SQL(sql string) *DeleteBuilder {
db.injection.SQL(db.marker, sql)
return db
}
+37
View File
@@ -7,6 +7,20 @@ import (
"fmt"
)
func ExampleDeleteFrom() {
sql := DeleteFrom("demo.user").
Where(
"status = 1",
).
Limit(10).
String()
fmt.Println(sql)
// Output:
// DELETE FROM demo.user WHERE status = 1 LIMIT 10
}
func ExampleDeleteBuilder() {
db := NewDeleteBuilder()
db.DeleteFrom("demo.user")
@@ -28,3 +42,26 @@ func ExampleDeleteBuilder() {
// DELETE FROM demo.user WHERE id > ? AND name LIKE ? AND (id_card IS NULL OR status IN (?, ?, ?)) AND modified_at > created_at + ?
// [1234 %Du 1 2 5 86400]
}
func ExampleDeleteBuilder_SQL() {
db := NewDeleteBuilder()
db.SQL(`/* before */`)
db.DeleteFrom("demo.user")
db.SQL("PARTITION (p0)")
db.Where(
db.GreaterThan("id", 1234),
)
db.SQL("/* after where */")
db.OrderBy("id")
db.SQL("/* after order by */")
db.Limit(10)
db.SQL("/* after limit */")
sql, args := db.Build()
fmt.Println(sql)
fmt.Println(args)
// Output:
// /* before */ DELETE FROM demo.user PARTITION (p0) WHERE id > ? /* after where */ ORDER BY id /* after order by */ LIMIT 10 /* after limit */
// [1234]
}
+3 -10
View File
@@ -104,16 +104,9 @@ func (f Flavor) NewUpdateBuilder() *UpdateBuilder {
return b
}
// Union unions all builders together using UNION operator with flavor.
func (f Flavor) Union(builders ...Builder) *UnionBuilder {
b := newUnionBuilder(unionDistinct, builders...)
b.SetFlavor(f)
return b
}
// UnionAll unions all builders together using UNION ALL operator with flavor.
func (f Flavor) UnionAll(builders ...Builder) *UnionBuilder {
b := newUnionBuilder(unionAll, builders...)
// NewUnionBuilder creates a new UNION builder with flavor.
func (f Flavor) NewUnionBuilder() *UnionBuilder {
b := newUnionBuilder()
b.SetFlavor(f)
return b
}
+56
View File
@@ -0,0 +1,56 @@
// Copyright 2018 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.
package sqlbuilder
import (
"bytes"
"strings"
)
// injection is a helper type to manage injected SQLs in all builders.
type injection struct {
markerSQLs map[injectionMarker][]string
}
type injectionMarker int
type injectedSQL struct {
marker injectionMarker
sql string
}
// newInjection creates a new injection.
func newInjection() *injection {
return &injection{
markerSQLs: map[injectionMarker][]string{},
}
}
// SQL adds sql to injection's sql list.
// All sqls inside injection is ordered by marker in ascending order.
func (injection *injection) SQL(marker injectionMarker, sql string) {
injection.markerSQLs[marker] = append(injection.markerSQLs[marker], sql)
}
// WriteTo joins all SQL strings at the same marker value with blank (" ")
// and writes the joined value to buf.
func (injection *injection) WriteTo(buf *bytes.Buffer, marker injectionMarker) {
sqls := injection.markerSQLs[marker]
notEmpty := buf.Len() > 0
if len(sqls) == 0 {
return
}
if notEmpty {
buf.WriteRune(' ')
}
s := strings.Join(sqls, " ")
buf.WriteString(s)
if !notEmpty {
buf.WriteRune(' ')
}
}
+51 -2
View File
@@ -9,6 +9,13 @@ import (
"strings"
)
const (
insertMarkerInit injectionMarker = iota
insertMarkerAfterInsertInto
insertMarkerAfterCols
insertMarkerAfterValues
)
// NewInsertBuilder creates a new INSERT builder.
func NewInsertBuilder() *InsertBuilder {
return DefaultFlavor.NewInsertBuilder()
@@ -17,8 +24,9 @@ func NewInsertBuilder() *InsertBuilder {
func newInsertBuilder() *InsertBuilder {
args := &Args{}
return &InsertBuilder{
verb: "INSERT",
args: args,
verb: "INSERT",
args: args,
injection: newInjection(),
}
}
@@ -30,34 +38,57 @@ type InsertBuilder struct {
values [][]string
args *Args
injection *injection
marker injectionMarker
}
var _ Builder = new(InsertBuilder)
// InsertInto sets table name in INSERT.
func InsertInto(table string) *InsertBuilder {
return DefaultFlavor.NewInsertBuilder().InsertInto(table)
}
// InsertInto sets table name in INSERT.
func (ib *InsertBuilder) InsertInto(table string) *InsertBuilder {
ib.table = Escape(table)
ib.marker = insertMarkerAfterInsertInto
return ib
}
// InsertIgnoreInto sets table name in INSERT IGNORE.
func InsertIgnoreInto(table string) *InsertBuilder {
return DefaultFlavor.NewInsertBuilder().InsertIgnoreInto(table)
}
// InsertIgnoreInto sets table name in INSERT IGNORE.
func (ib *InsertBuilder) InsertIgnoreInto(table string) *InsertBuilder {
ib.verb = "INSERT IGNORE"
ib.table = Escape(table)
ib.marker = insertMarkerAfterInsertInto
return ib
}
// ReplaceInto sets table name and changes the verb of ib to REPLACE.
// REPLACE INTO is a MySQL extension to the SQL standard.
func ReplaceInto(table string) *InsertBuilder {
return DefaultFlavor.NewInsertBuilder().ReplaceInto(table)
}
// ReplaceInto sets table name and changes the verb of ib to REPLACE.
// REPLACE INTO is a MySQL extension to the SQL standard.
func (ib *InsertBuilder) ReplaceInto(table string) *InsertBuilder {
ib.verb = "REPLACE"
ib.table = Escape(table)
ib.marker = insertMarkerAfterInsertInto
return ib
}
// Cols sets columns in INSERT.
func (ib *InsertBuilder) Cols(col ...string) *InsertBuilder {
ib.cols = EscapeAll(col...)
ib.marker = insertMarkerAfterCols
return ib
}
@@ -70,6 +101,7 @@ func (ib *InsertBuilder) Values(value ...interface{}) *InsertBuilder {
}
ib.values = append(ib.values, placeholders)
ib.marker = insertMarkerAfterValues
return ib
}
@@ -89,14 +121,18 @@ func (ib *InsertBuilder) Build() (sql string, args []interface{}) {
// They can be used in `DB#Query` of package `database/sql` directly.
func (ib *InsertBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{}) (sql string, args []interface{}) {
buf := &bytes.Buffer{}
ib.injection.WriteTo(buf, insertMarkerInit)
buf.WriteString(ib.verb)
buf.WriteString(" INTO ")
buf.WriteString(ib.table)
ib.injection.WriteTo(buf, insertMarkerAfterInsertInto)
if len(ib.cols) > 0 {
buf.WriteString(" (")
buf.WriteString(strings.Join(ib.cols, ", "))
buf.WriteString(")")
ib.injection.WriteTo(buf, insertMarkerAfterCols)
}
buf.WriteString(" VALUES ")
@@ -107,6 +143,8 @@ func (ib *InsertBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{
}
buf.WriteString(strings.Join(values, ", "))
ib.injection.WriteTo(buf, insertMarkerAfterValues)
return ib.args.CompileWithFlavor(buf.String(), flavor, initialArg...)
}
@@ -116,3 +154,14 @@ func (ib *InsertBuilder) SetFlavor(flavor Flavor) (old Flavor) {
ib.args.Flavor = flavor
return
}
// Var returns a placeholder for value.
func (ib *InsertBuilder) Var(arg interface{}) string {
return ib.args.Add(arg)
}
// SQL adds an arbitrary sql to current position.
func (ib *InsertBuilder) SQL(sql string) *InsertBuilder {
ib.injection.SQL(ib.marker, sql)
return ib
}
+61
View File
@@ -7,6 +7,48 @@ import (
"fmt"
)
func ExampleInsertInto() {
sql, args := InsertInto("demo.user").
Cols("id", "name", "status").
Values(4, "Sample", 2).
Build()
fmt.Println(sql)
fmt.Println(args)
// Output:
// INSERT INTO demo.user (id, name, status) VALUES (?, ?, ?)
// [4 Sample 2]
}
func ExampleInsertIgnoreInto() {
sql, args := InsertIgnoreInto("demo.user").
Cols("id", "name", "status").
Values(4, "Sample", 2).
Build()
fmt.Println(sql)
fmt.Println(args)
// Output:
// INSERT IGNORE INTO demo.user (id, name, status) VALUES (?, ?, ?)
// [4 Sample 2]
}
func ExampleReplaceInto() {
sql, args := ReplaceInto("demo.user").
Cols("id", "name", "status").
Values(4, "Sample", 2).
Build()
fmt.Println(sql)
fmt.Println(args)
// Output:
// REPLACE INTO demo.user (id, name, status) VALUES (?, ?, ?)
// [4 Sample 2]
}
func ExampleInsertBuilder() {
ib := NewInsertBuilder()
ib.InsertInto("demo.user")
@@ -54,3 +96,22 @@ func ExampleInsertBuilder_replaceInto() {
// REPLACE INTO demo.user (id, name, status, created_at) VALUES (?, ?, ?, UNIX_TIMESTAMP(NOW())), (?, ?, ?, ?)
// [1 Huan Du 1 2 Charmy Liu 1 1234567890]
}
func ExampleInsertBuilder_SQL() {
ib := NewInsertBuilder()
ib.SQL("/* before */")
ib.InsertInto("demo.user")
ib.SQL("PARTITION (p0)")
ib.Cols("id", "name", "status", "created_at")
ib.SQL("/* after cols */")
ib.Values(3, "Shawn Du", 1, 1234567890)
ib.SQL(ib.Var(Build("ON DUPLICATE KEY UPDATE status = $?", 1)))
sql, args := ib.Build()
fmt.Println(sql)
fmt.Println(args)
// Output:
// /* before */ INSERT INTO demo.user PARTITION (p0) (id, name, status, created_at) /* after cols */ VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE status = ?
// [3 Shawn Du 1 1234567890 1]
}
+88 -7
View File
@@ -10,6 +10,18 @@ import (
"strings"
)
const (
selectMarkerInit injectionMarker = iota
selectMarkerAfterSelect
selectMarkerAfterFrom
selectMarkerAfterJoin
selectMarkerAfterWhere
selectMarkerAfterGroupBy
selectMarkerAfterOrderBy
selectMarkerAfterLimit
selectMarkerAfterFor
)
// JoinOption is the option in JOIN.
type JoinOption string
@@ -35,9 +47,10 @@ func newSelectBuilder() *SelectBuilder {
Cond: Cond{
Args: args,
},
limit: -1,
offset: -1,
args: args,
limit: -1,
offset: -1,
args: args,
injection: newInjection(),
}
}
@@ -58,27 +71,39 @@ type SelectBuilder struct {
order string
limit int
offset int
forWhat string
args *Args
injection *injection
marker injectionMarker
}
var _ Builder = new(SelectBuilder)
// Distinct marks this SELECT as DISTINCT.
func (sb *SelectBuilder) Distinct() *SelectBuilder {
sb.distinct = true
return sb
// Select sets columns in SELECT.
func Select(col ...string) *SelectBuilder {
return DefaultFlavor.NewSelectBuilder().Select(col...)
}
// Select sets columns in SELECT.
func (sb *SelectBuilder) Select(col ...string) *SelectBuilder {
sb.selectCols = col
sb.marker = selectMarkerAfterSelect
return sb
}
// Distinct marks this SELECT as DISTINCT.
func (sb *SelectBuilder) Distinct() *SelectBuilder {
sb.distinct = true
sb.marker = selectMarkerAfterSelect
return sb
}
// From sets table names in SELECT.
func (sb *SelectBuilder) From(table ...string) *SelectBuilder {
sb.tables = table
sb.marker = selectMarkerAfterFrom
return sb
}
@@ -87,6 +112,7 @@ func (sb *SelectBuilder) From(table ...string) *SelectBuilder {
// It builds a JOIN expression like
// JOIN table ON onExpr[0] AND onExpr[1] ...
func (sb *SelectBuilder) Join(table string, onExpr ...string) *SelectBuilder {
sb.marker = selectMarkerAfterJoin
return sb.JoinWithOption("", table, onExpr...)
}
@@ -107,54 +133,77 @@ func (sb *SelectBuilder) JoinWithOption(option JoinOption, table string, onExpr
sb.joinOptions = append(sb.joinOptions, option)
sb.joinTables = append(sb.joinTables, table)
sb.joinExprs = append(sb.joinExprs, onExpr)
sb.marker = selectMarkerAfterJoin
return sb
}
// Where sets expressions of WHERE in SELECT.
func (sb *SelectBuilder) Where(andExpr ...string) *SelectBuilder {
sb.whereExprs = append(sb.whereExprs, andExpr...)
sb.marker = selectMarkerAfterWhere
return sb
}
// Having sets expressions of HAVING in SELECT.
func (sb *SelectBuilder) Having(andExpr ...string) *SelectBuilder {
sb.havingExprs = append(sb.havingExprs, andExpr...)
sb.marker = selectMarkerAfterGroupBy
return sb
}
// GroupBy sets columns of GROUP BY in SELECT.
func (sb *SelectBuilder) GroupBy(col ...string) *SelectBuilder {
sb.groupByCols = col
sb.marker = selectMarkerAfterGroupBy
return sb
}
// OrderBy sets columns of ORDER BY in SELECT.
func (sb *SelectBuilder) OrderBy(col ...string) *SelectBuilder {
sb.orderByCols = col
sb.marker = selectMarkerAfterOrderBy
return sb
}
// Asc sets order of ORDER BY to ASC.
func (sb *SelectBuilder) Asc() *SelectBuilder {
sb.order = "ASC"
sb.marker = selectMarkerAfterOrderBy
return sb
}
// Desc sets order of ORDER BY to DESC.
func (sb *SelectBuilder) Desc() *SelectBuilder {
sb.order = "DESC"
sb.marker = selectMarkerAfterOrderBy
return sb
}
// Limit sets the LIMIT in SELECT.
func (sb *SelectBuilder) Limit(limit int) *SelectBuilder {
sb.limit = limit
sb.marker = selectMarkerAfterLimit
return sb
}
// Offset sets the LIMIT offset in SELECT.
func (sb *SelectBuilder) Offset(offset int) *SelectBuilder {
sb.offset = offset
sb.marker = selectMarkerAfterLimit
return sb
}
// ForUpdate adds FOR UPDATE at the end of SELECT statement.
func (sb *SelectBuilder) ForUpdate() *SelectBuilder {
sb.forWhat = "UPDATE"
sb.marker = selectMarkerAfterFor
return sb
}
// ForShare adds FOR SHARE at the end of SELECT statement.
func (sb *SelectBuilder) ForShare() *SelectBuilder {
sb.forWhat = "SHARE"
sb.marker = selectMarkerAfterFor
return sb
}
@@ -185,6 +234,7 @@ func (sb *SelectBuilder) Build() (sql string, args []interface{}) {
// They can be used in `DB#Query` of package `database/sql` directly.
func (sb *SelectBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{}) (sql string, args []interface{}) {
buf := &bytes.Buffer{}
sb.injection.WriteTo(buf, selectMarkerInit)
buf.WriteString("SELECT ")
if sb.distinct {
@@ -192,8 +242,11 @@ func (sb *SelectBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{
}
buf.WriteString(strings.Join(sb.selectCols, ", "))
sb.injection.WriteTo(buf, selectMarkerAfterSelect)
buf.WriteString(" FROM ")
buf.WriteString(strings.Join(sb.tables, ", "))
sb.injection.WriteTo(buf, selectMarkerAfterFrom)
for i := range sb.joinTables {
if option := sb.joinOptions[i]; option != "" {
@@ -208,11 +261,18 @@ func (sb *SelectBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{
buf.WriteString(" ON ")
buf.WriteString(strings.Join(sb.joinExprs[i], " AND "))
}
}
if len(sb.joinTables) > 0 {
sb.injection.WriteTo(buf, selectMarkerAfterJoin)
}
if len(sb.whereExprs) > 0 {
buf.WriteString(" WHERE ")
buf.WriteString(strings.Join(sb.whereExprs, " AND "))
sb.injection.WriteTo(buf, selectMarkerAfterWhere)
}
if len(sb.groupByCols) > 0 {
@@ -223,6 +283,8 @@ func (sb *SelectBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{
buf.WriteString(" HAVING ")
buf.WriteString(strings.Join(sb.havingExprs, " AND "))
}
sb.injection.WriteTo(buf, selectMarkerAfterGroupBy)
}
if len(sb.orderByCols) > 0 {
@@ -233,6 +295,8 @@ func (sb *SelectBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{
buf.WriteRune(' ')
buf.WriteString(sb.order)
}
sb.injection.WriteTo(buf, selectMarkerAfterOrderBy)
}
if sb.limit >= 0 {
@@ -247,6 +311,17 @@ func (sb *SelectBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{
}
}
if sb.limit >= 0 {
sb.injection.WriteTo(buf, selectMarkerAfterLimit)
}
if sb.forWhat != "" {
buf.WriteString(" FOR ")
buf.WriteString(sb.forWhat)
sb.injection.WriteTo(buf, selectMarkerAfterFor)
}
return sb.args.CompileWithFlavor(buf.String(), flavor, initialArg...)
}
@@ -256,3 +331,9 @@ func (sb *SelectBuilder) SetFlavor(flavor Flavor) (old Flavor) {
sb.args.Flavor = flavor
return
}
// SQL adds an arbitrary sql to current position.
func (sb *SelectBuilder) SQL(sql string) *SelectBuilder {
sb.injection.SQL(sb.marker, sql)
return sb
}
+66
View File
@@ -8,6 +8,25 @@ import (
"fmt"
)
func ExampleSelect() {
// Build a SQL to create a HIVE table.
sql := CreateTable("users").
SQL("PARTITION BY (year)").
SQL("AS").
SQL(
Select("columns[0] id", "columns[1] name", "columns[2] year").
From("`all-users.csv`").
Limit(100).
String(),
).
String()
fmt.Println(sql)
// Output:
// CREATE TABLE users PARTITION BY (year) AS 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"))
@@ -153,6 +172,21 @@ func ExampleSelectBuilder_limit_offset() {
// SELECT * FROM user LIMIT 0
}
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.
@@ -170,3 +204,35 @@ func ExampleSelectBuilder_varInCols() {
// 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 */")
sql := sb.String()
fmt.Println(sql)
// 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 10 /* after limit */ FOR SHARE /* after for */
}
+69 -14
View File
@@ -14,6 +14,28 @@ const (
unionAll = " UNION ALL "
)
const (
unionMarkerInit injectionMarker = iota
unionMarkerAfterUnion
unionMarkerAfterOrderBy
unionMarkerAfterLimit
)
// NewUnionBuilder creates a new UNION builder.
func NewUnionBuilder() *UnionBuilder {
return DefaultFlavor.NewUnionBuilder()
}
func newUnionBuilder() *UnionBuilder {
return &UnionBuilder{
limit: -1,
offset: -1,
args: &Args{},
injection: newInjection(),
}
}
// UnionBuilder is a builder to build UNION.
type UnionBuilder struct {
format string
@@ -24,75 +46,88 @@ type UnionBuilder struct {
offset int
args *Args
injection *injection
marker injectionMarker
}
var _ Builder = new(UnionBuilder)
// Union unions all builders together using UNION operator.
func Union(builders ...Builder) *UnionBuilder {
return DefaultFlavor.Union(builders...)
return DefaultFlavor.NewUnionBuilder().Union(builders...)
}
// Union unions all builders together using UNION operator.
func (ub *UnionBuilder) Union(builders ...Builder) *UnionBuilder {
return ub.union(unionDistinct, builders...)
}
// UnionAll unions all builders together using UNION ALL operator.
func UnionAll(builders ...Builder) *UnionBuilder {
return DefaultFlavor.UnionAll(builders...)
return DefaultFlavor.NewUnionBuilder().UnionAll(builders...)
}
func newUnionBuilder(opt string, builders ...Builder) *UnionBuilder {
args := &Args{}
// UnionAll unions all builders together using UNION ALL operator.
func (ub *UnionBuilder) UnionAll(builders ...Builder) *UnionBuilder {
return ub.union(unionAll, builders...)
}
func (ub *UnionBuilder) union(opt string, builders ...Builder) *UnionBuilder {
buf := &bytes.Buffer{}
if len(builders) > 0 {
buf.Grow(len(builders) * (4 + len(opt)))
buf.WriteRune('(')
buf.WriteString(args.Add(builders[0]))
buf.WriteString(ub.Var(builders[0]))
buf.WriteRune(')')
for _, b := range builders[1:] {
buf.WriteString(opt)
buf.WriteRune('(')
buf.WriteString(args.Add(b))
buf.WriteString(ub.Var(b))
buf.WriteRune(')')
}
}
return &UnionBuilder{
format: buf.String(),
builders: builders,
limit: -1,
offset: -1,
args: args,
}
ub.format = buf.String()
ub.builders = builders
ub.marker = unionMarkerAfterUnion
return ub
}
// OrderBy sets columns of ORDER BY in SELECT.
func (ub *UnionBuilder) OrderBy(col ...string) *UnionBuilder {
ub.orderByCols = col
ub.marker = unionMarkerAfterOrderBy
return ub
}
// Asc sets order of ORDER BY to ASC.
func (ub *UnionBuilder) Asc() *UnionBuilder {
ub.order = "ASC"
ub.marker = unionMarkerAfterOrderBy
return ub
}
// Desc sets order of ORDER BY to DESC.
func (ub *UnionBuilder) Desc() *UnionBuilder {
ub.order = "DESC"
ub.marker = unionMarkerAfterOrderBy
return ub
}
// Limit sets the LIMIT in SELECT.
func (ub *UnionBuilder) Limit(limit int) *UnionBuilder {
ub.limit = limit
ub.marker = unionMarkerAfterLimit
return ub
}
// Offset sets the LIMIT offset in SELECT.
func (ub *UnionBuilder) Offset(offset int) *UnionBuilder {
ub.offset = offset
ub.marker = unionMarkerAfterLimit
return ub
}
@@ -112,7 +147,9 @@ func (ub *UnionBuilder) Build() (sql string, args []interface{}) {
// They can be used in `DB#Query` of package `database/sql` directly.
func (ub *UnionBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{}) (sql string, args []interface{}) {
buf := &bytes.Buffer{}
ub.injection.WriteTo(buf, unionMarkerInit)
buf.WriteString(ub.format)
ub.injection.WriteTo(buf, unionMarkerAfterUnion)
if len(ub.orderByCols) > 0 {
buf.WriteString(" ORDER BY ")
@@ -122,11 +159,14 @@ func (ub *UnionBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{}
buf.WriteRune(' ')
buf.WriteString(ub.order)
}
ub.injection.WriteTo(buf, unionMarkerAfterOrderBy)
}
if ub.limit >= 0 {
buf.WriteString(" LIMIT ")
buf.WriteString(strconv.Itoa(ub.limit))
}
if MySQL == flavor && ub.limit >= 0 || PostgreSQL == flavor {
@@ -136,6 +176,10 @@ func (ub *UnionBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{}
}
}
if ub.limit >= 0 {
ub.injection.WriteTo(buf, unionMarkerAfterLimit)
}
return ub.args.CompileWithFlavor(buf.String(), flavor, initialArg...)
}
@@ -145,3 +189,14 @@ func (ub *UnionBuilder) SetFlavor(flavor Flavor) (old Flavor) {
ub.args.Flavor = flavor
return
}
// Var returns a placeholder for value.
func (ub *UnionBuilder) Var(arg interface{}) string {
return ub.args.Add(arg)
}
// SQL adds an arbitrary sql to current position.
func (ub *UnionBuilder) SQL(sql string) *UnionBuilder {
ub.injection.SQL(ub.marker, sql)
return ub
}
+25
View File
@@ -52,3 +52,28 @@ func ExampleUnionAll() {
// (SELECT id, name, created_at FROM demo.user WHERE id > ?) UNION ALL (TABLE demo.user_profile) ORDER BY created_at ASC LIMIT 100 OFFSET 5
// [1234]
}
func ExampleUnionBuilder_SQL() {
sb1 := NewSelectBuilder()
sb1.Select("id", "name", "created_at")
sb1.From("demo.user")
sb2 := newSelectBuilder()
sb2.Select("id", "avatar")
sb2.From("demo.user_profile")
ub := NewUnionBuilder()
ub.SQL("/* before */")
ub.Union(sb1, sb2)
ub.SQL("/* after union */")
ub.OrderBy("created_at").Desc()
ub.SQL("/* after order by */")
ub.Limit(100).Offset(5)
ub.SQL("/* after limit */")
sql := ub.String()
fmt.Println(sql)
// Output:
// /* before */ (SELECT id, name, created_at FROM demo.user) UNION (SELECT id, avatar FROM demo.user_profile) /* after union */ ORDER BY created_at DESC /* after order by */ LIMIT 100 OFFSET 5 /* after limit */
}
+86 -1
View File
@@ -6,9 +6,19 @@ package sqlbuilder
import (
"bytes"
"fmt"
"strconv"
"strings"
)
const (
updateMarkerInit injectionMarker = iota
updateMarkerAfterUpdate
updateMarkerAfterSet
updateMarkerAfterWhere
updateMarkerAfterOrderBy
updateMarkerAfterLimit
)
// NewUpdateBuilder creates a new UPDATE builder.
func NewUpdateBuilder() *UpdateBuilder {
return DefaultFlavor.NewUpdateBuilder()
@@ -20,7 +30,9 @@ func newUpdateBuilder() *UpdateBuilder {
Cond: Cond{
Args: args,
},
args: args,
limit: -1,
args: args,
injection: newInjection(),
}
}
@@ -31,33 +43,48 @@ type UpdateBuilder struct {
table string
assignments []string
whereExprs []string
orderByCols []string
order string
limit int
args *Args
injection *injection
marker injectionMarker
}
var _ Builder = new(UpdateBuilder)
// Update sets table name in UPDATE.
func Update(table string) *UpdateBuilder {
return DefaultFlavor.NewUpdateBuilder().Update(table)
}
// Update sets table name in UPDATE.
func (ub *UpdateBuilder) Update(table string) *UpdateBuilder {
ub.table = Escape(table)
ub.marker = updateMarkerAfterUpdate
return ub
}
// Set sets the assignements in SET.
func (ub *UpdateBuilder) Set(assignment ...string) *UpdateBuilder {
ub.assignments = assignment
ub.marker = updateMarkerAfterSet
return ub
}
// SetMore appends the assignements in SET.
func (ub *UpdateBuilder) SetMore(assignment ...string) *UpdateBuilder {
ub.assignments = append(ub.assignments, assignment...)
ub.marker = updateMarkerAfterSet
return ub
}
// Where sets expressions of WHERE in UPDATE.
func (ub *UpdateBuilder) Where(andExpr ...string) *UpdateBuilder {
ub.whereExprs = append(ub.whereExprs, andExpr...)
ub.marker = updateMarkerAfterWhere
return ub
}
@@ -102,6 +129,34 @@ func (ub *UpdateBuilder) Div(field string, value interface{}) string {
return fmt.Sprintf("%s = %s / %s", f, f, ub.args.Add(value))
}
// OrderBy sets columns of ORDER BY in UPDATE.
func (ub *UpdateBuilder) OrderBy(col ...string) *UpdateBuilder {
ub.orderByCols = col
ub.marker = updateMarkerAfterOrderBy
return ub
}
// Asc sets order of ORDER BY to ASC.
func (ub *UpdateBuilder) Asc() *UpdateBuilder {
ub.order = "ASC"
ub.marker = updateMarkerAfterOrderBy
return ub
}
// Desc sets order of ORDER BY to DESC.
func (ub *UpdateBuilder) Desc() *UpdateBuilder {
ub.order = "DESC"
ub.marker = updateMarkerAfterOrderBy
return ub
}
// Limit sets the LIMIT in UPDATE.
func (ub *UpdateBuilder) Limit(limit int) *UpdateBuilder {
ub.limit = limit
ub.marker = updateMarkerAfterLimit
return ub
}
// String returns the compiled UPDATE string.
func (ub *UpdateBuilder) String() string {
s, _ := ub.Build()
@@ -118,14 +173,38 @@ func (ub *UpdateBuilder) Build() (sql string, args []interface{}) {
// They can be used in `DB#Query` of package `database/sql` directly.
func (ub *UpdateBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{}) (sql string, args []interface{}) {
buf := &bytes.Buffer{}
ub.injection.WriteTo(buf, updateMarkerInit)
buf.WriteString("UPDATE ")
buf.WriteString(ub.table)
ub.injection.WriteTo(buf, updateMarkerAfterUpdate)
buf.WriteString(" SET ")
buf.WriteString(strings.Join(ub.assignments, ", "))
ub.injection.WriteTo(buf, updateMarkerAfterSet)
if len(ub.whereExprs) > 0 {
buf.WriteString(" WHERE ")
buf.WriteString(strings.Join(ub.whereExprs, " AND "))
ub.injection.WriteTo(buf, updateMarkerAfterWhere)
}
if len(ub.orderByCols) > 0 {
buf.WriteString(" ORDER BY ")
buf.WriteString(strings.Join(ub.orderByCols, ", "))
if ub.order != "" {
buf.WriteRune(' ')
buf.WriteString(ub.order)
}
ub.injection.WriteTo(buf, updateMarkerAfterOrderBy)
}
if ub.limit >= 0 {
buf.WriteString(" LIMIT ")
buf.WriteString(strconv.Itoa(ub.limit))
ub.injection.WriteTo(buf, updateMarkerAfterLimit)
}
return ub.args.CompileWithFlavor(buf.String(), flavor, initialArg...)
@@ -137,3 +216,9 @@ func (ub *UpdateBuilder) SetFlavor(flavor Flavor) (old Flavor) {
ub.args.Flavor = flavor
return
}
// SQL adds an arbitrary sql to current position.
func (ub *UpdateBuilder) SQL(sql string) *UpdateBuilder {
ub.injection.SQL(ub.marker, sql)
return ub
}
+47 -7
View File
@@ -8,6 +8,22 @@ import (
"testing"
)
func ExampleUpdate() {
sql := Update("demo.user").
Set(
"visited = visited + 1",
).
Where(
"id = 1234",
).
String()
fmt.Println(sql)
// Output:
// UPDATE demo.user SET visited = visited + 1 WHERE id = 1234
}
func ExampleUpdateBuilder() {
ub := NewUpdateBuilder()
ub.Update("demo.user")
@@ -25,13 +41,14 @@ func ExampleUpdateBuilder() {
),
"modified_at > created_at + "+ub.Var(86400), // It's allowed to write arbitrary SQL.
)
ub.OrderBy("id").Asc()
sql, args := ub.Build()
fmt.Println(sql)
fmt.Println(args)
// Output:
// UPDATE demo.user SET type = ?, credit = credit + 1, modified_at = UNIX_TIMESTAMP(NOW()) WHERE id > ? AND name LIKE ? AND (id_card IS NULL OR status IN (?, ?, ?)) AND modified_at > created_at + ?
// UPDATE demo.user SET type = ?, credit = credit + 1, modified_at = UNIX_TIMESTAMP(NOW()) WHERE id > ? AND name LIKE ? AND (id_card IS NULL OR status IN (?, ?, ?)) AND modified_at > created_at + ? ORDER BY id ASC
// [sys 1234 %Du 1 2 5 86400]
}
@@ -58,7 +75,7 @@ func TestUpdateAssignments(t *testing.T) {
}
}
func TestUpdateBuilder_SetMore(t *testing.T) {
func ExampleUpdateBuilder_SetMore() {
ub := NewUpdateBuilder()
ub.Update("demo.user")
ub.Set(
@@ -68,10 +85,33 @@ func TestUpdateBuilder_SetMore(t *testing.T) {
ub.SetMore(
"modified_at = UNIX_TIMESTAMP(NOW())", // It's allowed to write arbitrary SQL.
)
sql, args := ub.Build()
actual := fmt.Sprintf("%v|%v", sql, args)
if expected := "UPDATE demo.user SET type = ?, credit = credit + 1, modified_at = UNIX_TIMESTAMP(NOW())|[sys]"; actual != expected {
t.Fatalf("invalid assignment result. [expected:%v] [actual:%v]", expected, actual)
}
sql, args := ub.Build()
fmt.Println(sql)
fmt.Println(args)
// Output:
// UPDATE demo.user SET type = ?, credit = credit + 1, modified_at = UNIX_TIMESTAMP(NOW())
// [sys]
}
func ExampleUpdateBuilder_SQL() {
ub := NewUpdateBuilder()
ub.SQL("/* before */")
ub.Update("demo.user")
ub.SQL("/* after update */")
ub.Set(
ub.Assign("type", "sys"),
)
ub.SQL("/* after set */")
ub.OrderBy("id").Desc()
ub.SQL("/* after order by */")
ub.Limit(10)
ub.SQL("/* after limit */")
sql := ub.String()
fmt.Println(sql)
// Output:
// /* before */ UPDATE demo.user /* after update */ SET type = ? /* after set */ ORDER BY id DESC /* after order by */ LIMIT 10 /* after limit */
}