mirror of
https://github.com/hanzoai/sqlbuilder.git
synced 2026-10-01 00:37:42 +00:00
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:
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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%]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(' ')
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 */
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user