Files

264 lines
6.8 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 (
"fmt"
"strings"
"github.com/huandu/go-clone"
)
const (
insertMarkerInit injectionMarker = iota
insertMarkerAfterInsertInto
insertMarkerAfterCols
insertMarkerAfterValues
insertMarkerAfterSelect
insertMarkerAfterReturning
)
// NewInsertBuilder creates a new INSERT builder.
func NewInsertBuilder() *InsertBuilder {
return DefaultFlavor.NewInsertBuilder()
}
func newInsertBuilder() *InsertBuilder {
args := &Args{}
return &InsertBuilder{
verb: "INSERT",
args: args,
injection: newInjection(),
}
}
// Clone returns a deep copy of InsertBuilder.
// It's useful when you want to create a base builder and clone it to build similar queries.
func (ib *InsertBuilder) Clone() *InsertBuilder {
return clone.Clone(ib).(*InsertBuilder)
}
// InsertBuilder is a builder to build INSERT.
type InsertBuilder struct {
verb string
table string
cols []string
values [][]string
returning []string
args *Args
injection *injection
marker injectionMarker
sbHolder string
}
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.args.Flavor.PrepareInsertIgnore(table, ib)
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
}
// Select returns a new SelectBuilder to build a SELECT statement inside the INSERT INTO.
func (isb *InsertBuilder) Select(col ...string) *SelectBuilder {
sb := Select(col...)
isb.sbHolder = isb.args.Add(sb)
return sb
}
// Values adds a list of values for a row in INSERT.
func (ib *InsertBuilder) Values(value ...interface{}) *InsertBuilder {
placeholders := make([]string, 0, len(value))
for _, v := range value {
placeholders = append(placeholders, ib.args.Add(v))
}
ib.values = append(ib.values, placeholders)
ib.marker = insertMarkerAfterValues
return ib
}
// Returning sets returning columns.
// For DBMS that doesn't support RETURNING, e.g. MySQL, it will be ignored.
func (ib *InsertBuilder) Returning(col ...string) *InsertBuilder {
ib.returning = col
ib.marker = insertMarkerAfterReturning
return ib
}
// NumValue returns the number of values to insert.
func (ib *InsertBuilder) NumValue() int {
return len(ib.values)
}
// String returns the compiled INSERT string.
func (ib *InsertBuilder) String() string {
s, _ := ib.Build()
return s
}
// Build returns compiled INSERT string and args.
// They can be used in `DB#Query` of package `database/sql` directly.
func (ib *InsertBuilder) Build() (sql string, args []interface{}) {
return ib.BuildWithFlavor(ib.args.Flavor)
}
// BuildWithFlavor returns compiled INSERT string and args with flavor and initial args.
// 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 := newStringBuilder()
ib.injection.WriteTo(buf, insertMarkerInit)
if len(ib.values) > 1 && ib.args.Flavor == Oracle {
buf.WriteLeadingString(ib.verb)
buf.WriteString(" ALL")
for _, v := range ib.values {
if len(ib.table) > 0 {
buf.WriteString(" INTO ")
buf.WriteString(ib.table)
}
ib.injection.WriteTo(buf, insertMarkerAfterInsertInto)
if len(ib.cols) > 0 {
buf.WriteLeadingString("(")
buf.WriteStrings(ib.cols, ", ")
buf.WriteString(")")
ib.injection.WriteTo(buf, insertMarkerAfterCols)
}
buf.WriteLeadingString("VALUES ")
values := make([]string, 0, len(ib.values))
values = append(values, fmt.Sprintf("(%v)", strings.Join(v, ", ")))
buf.WriteStrings(values, ", ")
}
buf.WriteString(" SELECT 1 from DUAL")
ib.injection.WriteTo(buf, insertMarkerAfterValues)
return ib.args.CompileWithFlavor(buf.String(), flavor, initialArg...)
}
if len(ib.table) > 0 {
buf.WriteLeadingString(ib.verb)
buf.WriteString(" INTO ")
buf.WriteString(ib.table)
}
ib.injection.WriteTo(buf, insertMarkerAfterInsertInto)
if len(ib.cols) > 0 {
buf.WriteLeadingString("(")
buf.WriteStrings(ib.cols, ", ")
buf.WriteString(")")
ib.injection.WriteTo(buf, insertMarkerAfterCols)
}
if flavor == SQLServer {
if len(ib.returning) > 0 {
buf.WriteLeadingString("OUTPUT ")
buf.WriteStringsPrefixed("INSERTED.", ib.returning, ", ")
}
ib.injection.WriteTo(buf, insertMarkerAfterReturning)
}
if ib.sbHolder != "" {
buf.WriteString(" ")
buf.WriteString(ib.sbHolder)
ib.injection.WriteTo(buf, insertMarkerAfterSelect)
} else if len(ib.values) > 0 {
buf.WriteLeadingString("VALUES ")
values := make([]string, 0, len(ib.values))
for _, v := range ib.values {
values = append(values, fmt.Sprintf("(%v)", strings.Join(v, ", ")))
}
buf.WriteStrings(values, ", ")
}
ib.injection.WriteTo(buf, insertMarkerAfterValues)
if flavor == PostgreSQL || flavor == SQLite {
if len(ib.returning) > 0 {
buf.WriteLeadingString("RETURNING ")
buf.WriteStrings(ib.returning, ", ")
}
ib.injection.WriteTo(buf, insertMarkerAfterReturning)
}
return ib.args.CompileWithFlavor(buf.String(), flavor, initialArg...)
}
// SetFlavor sets the flavor of compiled sql.
func (ib *InsertBuilder) SetFlavor(flavor Flavor) (old Flavor) {
old = ib.args.Flavor
ib.args.Flavor = flavor
return
}
// Flavor returns flavor of builder
func (ib *InsertBuilder) Flavor() Flavor {
return ib.args.Flavor
}
// 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
}