Files
sqlbuilder/delete.go
T

325 lines
8.5 KiB
Go

// Copyright 2018 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.
package sqlbuilder
import (
"reflect"
"github.com/huandu/go-clone"
)
const (
deleteMarkerInit injectionMarker = iota
deleteMarkerAfterWith
deleteMarkerAfterDeleteFrom
deleteMarkerAfterWhere
deleteMarkerAfterOrderBy
deleteMarkerAfterLimit
deleteMarkerAfterReturning
)
// NewDeleteBuilder creates a new DELETE builder.
func NewDeleteBuilder() *DeleteBuilder {
return DefaultFlavor.NewDeleteBuilder()
}
func newDeleteBuilder() *DeleteBuilder {
args := &Args{}
proxy := &whereClauseProxy{}
return &DeleteBuilder{
whereClauseProxy: proxy,
whereClauseExpr: args.Add(proxy),
Cond: Cond{
Args: args,
},
args: args,
injection: newInjection(),
}
}
// Clone returns a deep copy of DeleteBuilder.
// It's useful when you want to create a base builder and clone it to build similar queries.
func (db *DeleteBuilder) Clone() *DeleteBuilder {
return clone.Clone(db).(*DeleteBuilder)
}
func init() {
t := reflect.TypeOf(DeleteBuilder{})
clone.SetCustomFunc(t, func(allocator *clone.Allocator, old, new reflect.Value) {
cloned := allocator.CloneSlowly(old)
new.Set(cloned)
db := cloned.Addr().Interface().(*DeleteBuilder)
db.args.Replace(db.whereClauseExpr, db.whereClauseProxy)
db.args.Replace(db.cteBuilderVar, db.cteBuilder)
})
}
// DeleteBuilder is a builder to build DELETE.
type DeleteBuilder struct {
*WhereClause
Cond
whereClauseProxy *whereClauseProxy
whereClauseExpr string
cteBuilderVar string
cteBuilder *CTEBuilder
tables []string
orderByCols []string
order string
limitVar string
returning []string
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...)
}
// With sets WITH clause (the Common Table Expression) before DELETE.
func (db *DeleteBuilder) With(builder *CTEBuilder) *DeleteBuilder {
db.marker = deleteMarkerAfterWith
db.cteBuilderVar = db.Var(builder)
db.cteBuilder = builder
return db
}
// DeleteFrom sets table name in DELETE.
func (db *DeleteBuilder) DeleteFrom(table ...string) *DeleteBuilder {
db.tables = table
db.marker = deleteMarkerAfterDeleteFrom
return db
}
// TableNames returns all table names in this DELETE statement.
func (db *DeleteBuilder) TableNames() []string {
var additionalTableNames []string
if db.cteBuilder != nil {
additionalTableNames = db.cteBuilder.tableNamesForFrom()
}
var tableNames []string
if len(db.tables) > 0 && len(additionalTableNames) > 0 {
tableNames = make([]string, len(db.tables)+len(additionalTableNames))
copy(tableNames, db.tables)
copy(tableNames[len(db.tables):], additionalTableNames)
} else if len(db.tables) > 0 {
tableNames = db.tables
} else if len(additionalTableNames) > 0 {
tableNames = additionalTableNames
}
return tableNames
}
// Where adds expressions to the WHERE clause in DELETE.
//
// Multiple calls to Where will join expressions with AND.
// To reset the WHERE clause, set the WhereClause field to nil.
func (db *DeleteBuilder) Where(andExpr ...string) *DeleteBuilder {
if len(andExpr) == 0 || estimateStringsBytes(andExpr) == 0 {
return db
}
if db.WhereClause == nil {
db.WhereClause = NewWhereClause()
}
db.WhereClause.AddWhereExpr(db.args, andExpr...)
db.marker = deleteMarkerAfterWhere
return db
}
// AddWhereClause adds all clauses in the whereClause to SELECT.
func (db *DeleteBuilder) AddWhereClause(whereClause *WhereClause) *DeleteBuilder {
if db.WhereClause == nil {
db.WhereClause = NewWhereClause()
}
db.WhereClause.AddWhereClause(whereClause)
return db
}
// OrderBy sets columns of ORDER BY in DELETE.
//
// It's recommended to use OrderByAsc or OrderByDesc instead for better support of multiple ORDER BY columns with different directions.
// OrderBy combined with Asc/Desc only supports a single direction for all columns.
func (db *DeleteBuilder) OrderBy(col ...string) *DeleteBuilder {
db.orderByCols = col
db.marker = deleteMarkerAfterOrderBy
return db
}
// OrderByAsc sets a column of ORDER BY in DELETE with ASC order.
// It supports chaining multiple calls to add multiple ORDER BY columns with different directions.
//
// db.OrderByAsc("name").OrderByDesc("id")
// // Generates: ORDER BY name ASC, id DESC
func (db *DeleteBuilder) OrderByAsc(col string) *DeleteBuilder {
db.orderByCols = append(db.orderByCols, col+" ASC")
db.marker = deleteMarkerAfterOrderBy
return db
}
// OrderByDesc sets a column of ORDER BY in DELETE with DESC order.
// It supports chaining multiple calls to add multiple ORDER BY columns with different directions.
//
// db.OrderByDesc("id").OrderByAsc("name")
// // Generates: ORDER BY id DESC, name ASC
func (db *DeleteBuilder) OrderByDesc(col string) *DeleteBuilder {
db.orderByCols = append(db.orderByCols, col+" DESC")
db.marker = deleteMarkerAfterOrderBy
return db
}
// Asc sets order of ORDER BY to ASC.
//
// Deprecated: Use OrderByAsc instead. Asc only supports a single direction for all ORDER BY columns.
func (db *DeleteBuilder) Asc() *DeleteBuilder {
db.order = "ASC"
db.marker = deleteMarkerAfterOrderBy
return db
}
// Desc sets order of ORDER BY to DESC.
//
// Deprecated: Use OrderByDesc instead. Desc only supports a single direction for all ORDER BY columns.
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 {
if limit < 0 {
db.limitVar = ""
return db
}
db.limitVar = db.Var(limit)
db.marker = deleteMarkerAfterLimit
return db
}
// Returning sets returning columns.
// For DBMS that doesn't support RETURNING, e.g. MySQL, it will be ignored.
func (db *DeleteBuilder) Returning(col ...string) *DeleteBuilder {
db.returning = col
db.marker = deleteMarkerAfterReturning
return db
}
// String returns the compiled DELETE string.
func (db *DeleteBuilder) String() string {
s, _ := db.Build()
return s
}
// Build returns compiled DELETE string and args.
// They can be used in `DB#Query` of package `database/sql` directly.
func (db *DeleteBuilder) Build() (sql string, args []interface{}) {
return db.BuildWithFlavor(db.args.Flavor)
}
// BuildWithFlavor returns compiled DELETE string and args with flavor and initial args.
// 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 := newStringBuilder()
db.injection.WriteTo(buf, deleteMarkerInit)
if db.cteBuilder != nil {
buf.WriteLeadingString(db.cteBuilderVar)
db.injection.WriteTo(buf, deleteMarkerAfterWith)
}
tableNames := db.TableNames()
if len(tableNames) > 0 {
buf.WriteLeadingString("DELETE FROM ")
buf.WriteStrings(tableNames, ", ")
}
db.injection.WriteTo(buf, deleteMarkerAfterDeleteFrom)
if flavor == SQLServer {
if len(db.returning) > 0 {
buf.WriteLeadingString("OUTPUT ")
buf.WriteStringsPrefixed("DELETED.", db.returning, ", ")
}
db.injection.WriteTo(buf, insertMarkerAfterReturning)
}
if db.WhereClause != nil {
db.whereClauseProxy.WhereClause = db.WhereClause
defer func() {
db.whereClauseProxy.WhereClause = nil
}()
buf.WriteLeadingString(db.whereClauseExpr)
db.injection.WriteTo(buf, deleteMarkerAfterWhere)
}
if len(db.orderByCols) > 0 {
buf.WriteLeadingString("ORDER BY ")
buf.WriteStrings(db.orderByCols, ", ")
if db.order != "" {
buf.WriteRune(' ')
buf.WriteString(db.order)
}
db.injection.WriteTo(buf, deleteMarkerAfterOrderBy)
}
if len(db.limitVar) > 0 {
buf.WriteLeadingString("LIMIT ")
buf.WriteString(db.limitVar)
db.injection.WriteTo(buf, deleteMarkerAfterLimit)
}
if flavor == PostgreSQL || flavor == SQLite {
if len(db.returning) > 0 {
buf.WriteLeadingString("RETURNING ")
buf.WriteStrings(db.returning, ", ")
}
db.injection.WriteTo(buf, deleteMarkerAfterReturning)
}
return db.args.CompileWithFlavor(buf.String(), flavor, initialArg...)
}
// SetFlavor sets the flavor of compiled sql.
func (db *DeleteBuilder) SetFlavor(flavor Flavor) (old Flavor) {
old = db.args.Flavor
db.args.Flavor = flavor
return
}
// Flavor returns flavor of builder
func (db *DeleteBuilder) Flavor() Flavor {
return db.args.Flavor
}
// SQL adds an arbitrary sql to current position.
func (db *DeleteBuilder) SQL(sql string) *DeleteBuilder {
db.injection.SQL(db.marker, sql)
return db
}