Files
sqlbuilder/delete.go
T
Huan Du 6db1108ec3 Add OrderByAsc and OrderByDesc methods to all builders
Implement new OrderByAsc(col) and OrderByDesc(col) methods for
SelectBuilder, UnionBuilder, UpdateBuilder, and DeleteBuilder. These
methods allow chaining multiple ORDER BY columns with different sort
directions, addressing the limitation where the old OrderBy/Asc/Desc
pattern could only apply a single direction to all columns.

Key changes:
- Add OrderByAsc(col string) and OrderByDesc(col string) to all four builders
- Each method appends the column with explicit ASC/DESC suffix
- Support chaining for complex ordering: OrderByDesc("score").OrderByAsc("name")
- Deprecate OrderBy/Asc/Desc methods with clear migration guidance
- Add comprehensive tests (TestSelectBuilder_OrderByAscDesc)
- Add example functions demonstrating usage
- Update all existing examples to use new API
- Update README.md with new "Build ORDER BY clause" section

This design is inspired by MyBatis-Plus and provides a more intuitive
and flexible API for handling multiple ORDER BY columns with different
sorting directions.

fix #214
2025-10-10 16:13:30 +08:00

322 lines
8.4 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 sets expressions of WHERE in DELETE.
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.
//
// Deprecated: 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
}