|
|
@ -5,7 +5,9 @@ |
|
|
|
package builder |
|
|
|
|
|
|
|
import ( |
|
|
|
sql2 "database/sql" |
|
|
|
"fmt" |
|
|
|
"sort" |
|
|
|
) |
|
|
|
|
|
|
|
type optype byte |
|
|
@ -16,6 +18,15 @@ const ( |
|
|
|
insertType // insert
|
|
|
|
updateType // update
|
|
|
|
deleteType // delete
|
|
|
|
unionType // union
|
|
|
|
) |
|
|
|
|
|
|
|
const ( |
|
|
|
POSTGRES = "postgres" |
|
|
|
SQLITE = "sqlite3" |
|
|
|
MYSQL = "mysql" |
|
|
|
MSSQL = "mssql" |
|
|
|
ORACLE = "oracle" |
|
|
|
) |
|
|
|
|
|
|
|
type join struct { |
|
|
@ -24,68 +35,115 @@ type join struct { |
|
|
|
joinCond Cond |
|
|
|
} |
|
|
|
|
|
|
|
type union struct { |
|
|
|
unionType string |
|
|
|
builder *Builder |
|
|
|
} |
|
|
|
|
|
|
|
type limit struct { |
|
|
|
limitN int |
|
|
|
offset int |
|
|
|
} |
|
|
|
|
|
|
|
// Builder describes a SQL statement
|
|
|
|
type Builder struct { |
|
|
|
optype |
|
|
|
tableName string |
|
|
|
cond Cond |
|
|
|
selects []string |
|
|
|
joins []join |
|
|
|
inserts Eq |
|
|
|
updates []Eq |
|
|
|
orderBy string |
|
|
|
groupBy string |
|
|
|
having string |
|
|
|
dialect string |
|
|
|
isNested bool |
|
|
|
into string |
|
|
|
from string |
|
|
|
subQuery *Builder |
|
|
|
cond Cond |
|
|
|
selects []string |
|
|
|
joins []join |
|
|
|
unions []union |
|
|
|
limitation *limit |
|
|
|
insertCols []string |
|
|
|
insertVals []interface{} |
|
|
|
updates []Eq |
|
|
|
orderBy string |
|
|
|
groupBy string |
|
|
|
having string |
|
|
|
} |
|
|
|
|
|
|
|
// Dialect sets the db dialect of Builder.
|
|
|
|
func Dialect(dialect string) *Builder { |
|
|
|
builder := &Builder{cond: NewCond(), dialect: dialect} |
|
|
|
return builder |
|
|
|
} |
|
|
|
|
|
|
|
// Select creates a select Builder
|
|
|
|
func Select(cols ...string) *Builder { |
|
|
|
builder := &Builder{cond: NewCond()} |
|
|
|
return builder.Select(cols...) |
|
|
|
// MySQL is shortcut of Dialect(MySQL)
|
|
|
|
func MySQL() *Builder { |
|
|
|
return Dialect(MYSQL) |
|
|
|
} |
|
|
|
|
|
|
|
// Insert creates an insert Builder
|
|
|
|
func Insert(eq Eq) *Builder { |
|
|
|
builder := &Builder{cond: NewCond()} |
|
|
|
return builder.Insert(eq) |
|
|
|
// MsSQL is shortcut of Dialect(MsSQL)
|
|
|
|
func MsSQL() *Builder { |
|
|
|
return Dialect(MSSQL) |
|
|
|
} |
|
|
|
|
|
|
|
// Update creates an update Builder
|
|
|
|
func Update(updates ...Eq) *Builder { |
|
|
|
builder := &Builder{cond: NewCond()} |
|
|
|
return builder.Update(updates...) |
|
|
|
// Oracle is shortcut of Dialect(Oracle)
|
|
|
|
func Oracle() *Builder { |
|
|
|
return Dialect(ORACLE) |
|
|
|
} |
|
|
|
|
|
|
|
// Delete creates a delete Builder
|
|
|
|
func Delete(conds ...Cond) *Builder { |
|
|
|
builder := &Builder{cond: NewCond()} |
|
|
|
return builder.Delete(conds...) |
|
|
|
// Postgres is shortcut of Dialect(Postgres)
|
|
|
|
func Postgres() *Builder { |
|
|
|
return Dialect(POSTGRES) |
|
|
|
} |
|
|
|
|
|
|
|
// SQLite is shortcut of Dialect(SQLITE)
|
|
|
|
func SQLite() *Builder { |
|
|
|
return Dialect(SQLITE) |
|
|
|
} |
|
|
|
|
|
|
|
// Where sets where SQL
|
|
|
|
func (b *Builder) Where(cond Cond) *Builder { |
|
|
|
b.cond = b.cond.And(cond) |
|
|
|
if b.cond.IsValid() { |
|
|
|
b.cond = b.cond.And(cond) |
|
|
|
} else { |
|
|
|
b.cond = cond |
|
|
|
} |
|
|
|
return b |
|
|
|
} |
|
|
|
|
|
|
|
// From sets the table name
|
|
|
|
func (b *Builder) From(tableName string) *Builder { |
|
|
|
b.tableName = tableName |
|
|
|
// From sets from subject(can be a table name in string or a builder pointer) and its alias
|
|
|
|
func (b *Builder) From(subject interface{}, alias ...string) *Builder { |
|
|
|
switch subject.(type) { |
|
|
|
case *Builder: |
|
|
|
b.subQuery = subject.(*Builder) |
|
|
|
|
|
|
|
if len(alias) > 0 { |
|
|
|
b.from = alias[0] |
|
|
|
} else { |
|
|
|
b.isNested = true |
|
|
|
} |
|
|
|
case string: |
|
|
|
b.from = subject.(string) |
|
|
|
|
|
|
|
if len(alias) > 0 { |
|
|
|
b.from = b.from + " " + alias[0] |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return b |
|
|
|
} |
|
|
|
|
|
|
|
// TableName returns the table name
|
|
|
|
func (b *Builder) TableName() string { |
|
|
|
return b.tableName |
|
|
|
if b.optype == insertType { |
|
|
|
return b.into |
|
|
|
} |
|
|
|
return b.from |
|
|
|
} |
|
|
|
|
|
|
|
// Into sets insert table name
|
|
|
|
func (b *Builder) Into(tableName string) *Builder { |
|
|
|
b.tableName = tableName |
|
|
|
b.into = tableName |
|
|
|
return b |
|
|
|
} |
|
|
|
|
|
|
|
// Join sets join table and contions
|
|
|
|
// Join sets join table and conditions
|
|
|
|
func (b *Builder) Join(joinType, joinTable string, joinCond interface{}) *Builder { |
|
|
|
switch joinCond.(type) { |
|
|
|
case Cond: |
|
|
@ -97,6 +155,50 @@ func (b *Builder) Join(joinType, joinTable string, joinCond interface{}) *Builde |
|
|
|
return b |
|
|
|
} |
|
|
|
|
|
|
|
// Union sets union conditions
|
|
|
|
func (b *Builder) Union(unionTp string, unionCond *Builder) *Builder { |
|
|
|
var builder *Builder |
|
|
|
if b.optype != unionType { |
|
|
|
builder = &Builder{cond: NewCond()} |
|
|
|
builder.optype = unionType |
|
|
|
builder.dialect = b.dialect |
|
|
|
builder.selects = b.selects |
|
|
|
|
|
|
|
currentUnions := b.unions |
|
|
|
// erase sub unions (actually append to new Builder.unions)
|
|
|
|
b.unions = nil |
|
|
|
|
|
|
|
for e := range currentUnions { |
|
|
|
currentUnions[e].builder.dialect = b.dialect |
|
|
|
} |
|
|
|
|
|
|
|
builder.unions = append(append(builder.unions, union{"", b}), currentUnions...) |
|
|
|
} else { |
|
|
|
builder = b |
|
|
|
} |
|
|
|
|
|
|
|
if unionCond != nil { |
|
|
|
if unionCond.dialect == "" && builder.dialect != "" { |
|
|
|
unionCond.dialect = builder.dialect |
|
|
|
} |
|
|
|
|
|
|
|
builder.unions = append(builder.unions, union{unionTp, unionCond}) |
|
|
|
} |
|
|
|
|
|
|
|
return builder |
|
|
|
} |
|
|
|
|
|
|
|
// Limit sets limitN condition
|
|
|
|
func (b *Builder) Limit(limitN int, offset ...int) *Builder { |
|
|
|
b.limitation = &limit{limitN: limitN} |
|
|
|
|
|
|
|
if len(offset) > 0 { |
|
|
|
b.limitation.offset = offset[0] |
|
|
|
} |
|
|
|
|
|
|
|
return b |
|
|
|
} |
|
|
|
|
|
|
|
// InnerJoin sets inner join
|
|
|
|
func (b *Builder) InnerJoin(joinTable string, joinCond interface{}) *Builder { |
|
|
|
return b.Join("INNER", joinTable, joinCond) |
|
|
@ -125,7 +227,9 @@ func (b *Builder) FullJoin(joinTable string, joinCond interface{}) *Builder { |
|
|
|
// Select sets select SQL
|
|
|
|
func (b *Builder) Select(cols ...string) *Builder { |
|
|
|
b.selects = cols |
|
|
|
b.optype = selectType |
|
|
|
if b.optype == condType { |
|
|
|
b.optype = selectType |
|
|
|
} |
|
|
|
return b |
|
|
|
} |
|
|
|
|
|
|
@ -141,16 +245,70 @@ func (b *Builder) Or(cond Cond) *Builder { |
|
|
|
return b |
|
|
|
} |
|
|
|
|
|
|
|
type insertColsSorter struct { |
|
|
|
cols []string |
|
|
|
vals []interface{} |
|
|
|
} |
|
|
|
|
|
|
|
func (s insertColsSorter) Len() int { |
|
|
|
return len(s.cols) |
|
|
|
} |
|
|
|
func (s insertColsSorter) Swap(i, j int) { |
|
|
|
s.cols[i], s.cols[j] = s.cols[j], s.cols[i] |
|
|
|
s.vals[i], s.vals[j] = s.vals[j], s.vals[i] |
|
|
|
} |
|
|
|
|
|
|
|
func (s insertColsSorter) Less(i, j int) bool { |
|
|
|
return s.cols[i] < s.cols[j] |
|
|
|
} |
|
|
|
|
|
|
|
// Insert sets insert SQL
|
|
|
|
func (b *Builder) Insert(eq Eq) *Builder { |
|
|
|
b.inserts = eq |
|
|
|
func (b *Builder) Insert(eq ...interface{}) *Builder { |
|
|
|
if len(eq) > 0 { |
|
|
|
var paramType = -1 |
|
|
|
for _, e := range eq { |
|
|
|
switch t := e.(type) { |
|
|
|
case Eq: |
|
|
|
if paramType == -1 { |
|
|
|
paramType = 0 |
|
|
|
} |
|
|
|
if paramType != 0 { |
|
|
|
break |
|
|
|
} |
|
|
|
for k, v := range t { |
|
|
|
b.insertCols = append(b.insertCols, k) |
|
|
|
b.insertVals = append(b.insertVals, v) |
|
|
|
} |
|
|
|
case string: |
|
|
|
if paramType == -1 { |
|
|
|
paramType = 1 |
|
|
|
} |
|
|
|
if paramType != 1 { |
|
|
|
break |
|
|
|
} |
|
|
|
b.insertCols = append(b.insertCols, t) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if len(b.insertCols) == len(b.insertVals) { |
|
|
|
sort.Sort(insertColsSorter{ |
|
|
|
cols: b.insertCols, |
|
|
|
vals: b.insertVals, |
|
|
|
}) |
|
|
|
} |
|
|
|
b.optype = insertType |
|
|
|
return b |
|
|
|
} |
|
|
|
|
|
|
|
// Update sets update SQL
|
|
|
|
func (b *Builder) Update(updates ...Eq) *Builder { |
|
|
|
b.updates = updates |
|
|
|
b.updates = make([]Eq, 0, len(updates)) |
|
|
|
for _, update := range updates { |
|
|
|
if update.IsValid() { |
|
|
|
b.updates = append(b.updates, update) |
|
|
|
} |
|
|
|
} |
|
|
|
b.optype = updateType |
|
|
|
return b |
|
|
|
} |
|
|
@ -165,8 +323,8 @@ func (b *Builder) Delete(conds ...Cond) *Builder { |
|
|
|
// WriteTo implements Writer interface
|
|
|
|
func (b *Builder) WriteTo(w Writer) error { |
|
|
|
switch b.optype { |
|
|
|
case condType: |
|
|
|
return b.cond.WriteTo(w) |
|
|
|
/*case condType: |
|
|
|
return b.cond.WriteTo(w)*/ |
|
|
|
case selectType: |
|
|
|
return b.selectWriteTo(w) |
|
|
|
case insertType: |
|
|
@ -175,6 +333,8 @@ func (b *Builder) WriteTo(w Writer) error { |
|
|
|
return b.updateWriteTo(w) |
|
|
|
case deleteType: |
|
|
|
return b.deleteWriteTo(w) |
|
|
|
case unionType: |
|
|
|
return b.unionWriteTo(w) |
|
|
|
} |
|
|
|
|
|
|
|
return ErrNotSupportType |
|
|
@ -187,43 +347,48 @@ func (b *Builder) ToSQL() (string, []interface{}, error) { |
|
|
|
return "", nil, err |
|
|
|
} |
|
|
|
|
|
|
|
return w.writer.String(), w.args, nil |
|
|
|
} |
|
|
|
// in case of sql.NamedArg in args
|
|
|
|
for e := range w.args { |
|
|
|
if namedArg, ok := w.args[e].(sql2.NamedArg); ok { |
|
|
|
w.args[e] = namedArg.Value |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// ConvertPlaceholder replaces ? to $1, $2 ... or :1, :2 ... according prefix
|
|
|
|
func ConvertPlaceholder(sql, prefix string) (string, error) { |
|
|
|
buf := StringBuilder{} |
|
|
|
var j, start = 0, 0 |
|
|
|
for i := 0; i < len(sql); i++ { |
|
|
|
if sql[i] == '?' { |
|
|
|
_, err := buf.WriteString(sql[start:i]) |
|
|
|
if err != nil { |
|
|
|
return "", err |
|
|
|
} |
|
|
|
start = i + 1 |
|
|
|
var sql = w.writer.String() |
|
|
|
var err error |
|
|
|
|
|
|
|
_, err = buf.WriteString(prefix) |
|
|
|
if err != nil { |
|
|
|
return "", err |
|
|
|
} |
|
|
|
switch b.dialect { |
|
|
|
case ORACLE, MSSQL: |
|
|
|
// This is for compatibility with different sql drivers
|
|
|
|
for e := range w.args { |
|
|
|
w.args[e] = sql2.Named(fmt.Sprintf("p%d", e+1), w.args[e]) |
|
|
|
} |
|
|
|
|
|
|
|
j = j + 1 |
|
|
|
_, err = buf.WriteString(fmt.Sprintf("%d", j)) |
|
|
|
if err != nil { |
|
|
|
return "", err |
|
|
|
} |
|
|
|
var prefix string |
|
|
|
if b.dialect == ORACLE { |
|
|
|
prefix = ":p" |
|
|
|
} else { |
|
|
|
prefix = "@p" |
|
|
|
} |
|
|
|
|
|
|
|
if sql, err = ConvertPlaceholder(sql, prefix); err != nil { |
|
|
|
return "", nil, err |
|
|
|
} |
|
|
|
case POSTGRES: |
|
|
|
if sql, err = ConvertPlaceholder(sql, "$"); err != nil { |
|
|
|
return "", nil, err |
|
|
|
} |
|
|
|
} |
|
|
|
return buf.String(), nil |
|
|
|
|
|
|
|
return sql, w.args, nil |
|
|
|
} |
|
|
|
|
|
|
|
// ToSQL convert a builder or condtions to SQL and args
|
|
|
|
func ToSQL(cond interface{}) (string, []interface{}, error) { |
|
|
|
switch cond.(type) { |
|
|
|
case Cond: |
|
|
|
return condToSQL(cond.(Cond)) |
|
|
|
case *Builder: |
|
|
|
return cond.(*Builder).ToSQL() |
|
|
|
// ToBoundSQL
|
|
|
|
func (b *Builder) ToBoundSQL() (string, error) { |
|
|
|
w := NewWriter() |
|
|
|
if err := b.WriteTo(w); err != nil { |
|
|
|
return "", err |
|
|
|
} |
|
|
|
return "", nil, ErrNotSupportType |
|
|
|
|
|
|
|
return ConvertToBoundSQL(w.writer.String(), w.args) |
|
|
|
} |