mirror of
https://github.com/hanzoai/sqlbuilder.git
synced 2026-10-01 00:37:42 +00:00
476 lines
13 KiB
Go
476 lines
13 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 (
|
|
"bytes"
|
|
"reflect"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
// DBTag is the struct tag to describe the name for a field in struct.
|
|
DBTag = "db"
|
|
|
|
// FieldTag is the struct tag to describe the tag name for a field in struct.
|
|
// Use "," to separate different tags.
|
|
FieldTag = "fieldtag"
|
|
|
|
// FieldOpt is the options for a struct field.
|
|
// As db column can contain "," in theory, field options should be provided in a separated tag.
|
|
FieldOpt = "fieldopt"
|
|
)
|
|
|
|
const (
|
|
fieldOptWithQuote = "withquote"
|
|
fieldOptOmitEmpty = "omitempty"
|
|
)
|
|
|
|
// Struct represents a struct type.
|
|
//
|
|
// All methods in Struct are thread-safe.
|
|
// We can define a global variable to hold a Struct and use it in any goroutine.
|
|
type Struct struct {
|
|
Flavor Flavor
|
|
|
|
structType reflect.Type
|
|
fieldAlias map[string]string
|
|
taggedFields map[string][]string
|
|
quotedFields map[string]struct{}
|
|
omitEmptyFields map[string]struct{}
|
|
}
|
|
|
|
// NewStruct analyzes type information in structValue
|
|
// and creates a new Struct with all structValue fields.
|
|
// If structValue is not a struct, NewStruct returns a dummy Sturct.
|
|
func NewStruct(structValue interface{}) *Struct {
|
|
t := reflect.TypeOf(structValue)
|
|
t = dereferencedType(t)
|
|
s := &Struct{
|
|
Flavor: DefaultFlavor,
|
|
}
|
|
|
|
if t.Kind() != reflect.Struct {
|
|
return s
|
|
}
|
|
|
|
s.structType = t
|
|
s.fieldAlias = map[string]string{}
|
|
s.taggedFields = map[string][]string{}
|
|
s.quotedFields = map[string]struct{}{}
|
|
s.omitEmptyFields = map[string]struct{}{}
|
|
s.parse(t)
|
|
return s
|
|
}
|
|
|
|
// For sets the default flavor of s.
|
|
func (s *Struct) For(flavor Flavor) *Struct {
|
|
s.Flavor = flavor
|
|
return s
|
|
}
|
|
|
|
func (s *Struct) parse(t reflect.Type) {
|
|
l := t.NumField()
|
|
|
|
for i := 0; i < l; i++ {
|
|
field := t.Field(i)
|
|
|
|
if field.Anonymous {
|
|
ft := dereferencedType(field.Type)
|
|
s.parse(ft)
|
|
continue
|
|
}
|
|
|
|
// Parse DBTag.
|
|
dbtag := field.Tag.Get(DBTag)
|
|
alias := dbtag
|
|
|
|
if dbtag == "-" {
|
|
continue
|
|
}
|
|
|
|
if dbtag == "" {
|
|
alias = field.Name
|
|
s.fieldAlias[field.Name] = field.Name
|
|
} else {
|
|
s.fieldAlias[dbtag] = field.Name
|
|
}
|
|
|
|
// Parse FieldTag.
|
|
fieldtag := field.Tag.Get(FieldTag)
|
|
tags := strings.Split(fieldtag, ",")
|
|
|
|
for _, t := range tags {
|
|
if t != "" {
|
|
s.taggedFields[t] = append(s.taggedFields[t], alias)
|
|
}
|
|
}
|
|
|
|
s.taggedFields[""] = append(s.taggedFields[""], alias)
|
|
|
|
// Parse FieldOpt.
|
|
fieldopt := field.Tag.Get(FieldOpt)
|
|
opts := strings.Split(fieldopt, ",")
|
|
|
|
for _, opt := range opts {
|
|
switch opt {
|
|
case fieldOptWithQuote:
|
|
s.quotedFields[alias] = struct{}{}
|
|
case fieldOptOmitEmpty:
|
|
s.omitEmptyFields[alias] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// SelectFrom creates a new `SelectBuilder` with table name.
|
|
// By default, all exported fields of the s are listed as columns in SELECT.
|
|
//
|
|
// Caller is responsible to set WHERE condition to find right record.
|
|
func (s *Struct) SelectFrom(table string) *SelectBuilder {
|
|
return s.SelectFromForTag(table, "")
|
|
}
|
|
|
|
// SelectFromForTag creates a new `SelectBuilder` with table name for a specified tag.
|
|
// By default, all fields of the s tagged with tag are listed as columns in SELECT.
|
|
//
|
|
// Caller is responsible to set WHERE condition to find right record.
|
|
func (s *Struct) SelectFromForTag(table string, tag string) *SelectBuilder {
|
|
sb := s.Flavor.NewSelectBuilder()
|
|
sb.From(table)
|
|
|
|
if s.taggedFields == nil {
|
|
return sb
|
|
}
|
|
|
|
fields, ok := s.taggedFields[tag]
|
|
|
|
if ok {
|
|
fields = s.quoteFields(fields)
|
|
|
|
buf := &bytes.Buffer{}
|
|
cols := make([]string, 0, len(fields))
|
|
|
|
for _, field := range fields {
|
|
buf.WriteString(table)
|
|
buf.WriteRune('.')
|
|
buf.WriteString(field)
|
|
cols = append(cols, buf.String())
|
|
buf.Reset()
|
|
}
|
|
|
|
sb.Select(cols...)
|
|
} else {
|
|
sb.Select("*")
|
|
}
|
|
|
|
return sb
|
|
}
|
|
|
|
// Update creates a new `UpdateBuilder` with table name.
|
|
// By default, all exported fields of the s is assigned in UPDATE with the field values from value.
|
|
// If value's type is not the same as that of s, Update returns a dummy `UpdateBuilder` with table name.
|
|
//
|
|
// Caller is responsible to set WHERE condition to match right record.
|
|
func (s *Struct) Update(table string, value interface{}) *UpdateBuilder {
|
|
return s.UpdateForTag(table, "", value)
|
|
}
|
|
|
|
// UpdateForTag creates a new `UpdateBuilder` with table name.
|
|
// By default, all fields of the s tagged with tag is assigned in UPDATE with the field values from value.
|
|
// If value's type is not the same as that of s, UpdateForTag returns a dummy `UpdateBuilder` with table name.
|
|
//
|
|
// Caller is responsible to set WHERE condition to match right record.
|
|
func (s *Struct) UpdateForTag(table string, tag string, value interface{}) *UpdateBuilder {
|
|
ub := s.Flavor.NewUpdateBuilder()
|
|
ub.Update(table)
|
|
|
|
if s.taggedFields == nil {
|
|
return ub
|
|
}
|
|
|
|
fields, ok := s.taggedFields[tag]
|
|
|
|
if !ok {
|
|
return ub
|
|
}
|
|
|
|
v := reflect.ValueOf(value)
|
|
v = dereferencedValue(v)
|
|
|
|
if v.Type() != s.structType {
|
|
return ub
|
|
}
|
|
|
|
quoted := s.quoteFields(fields)
|
|
assignments := make([]string, 0, len(fields))
|
|
|
|
for i, f := range fields {
|
|
name := s.fieldAlias[f]
|
|
val := v.FieldByName(name)
|
|
|
|
if isEmptyValue(val) {
|
|
if _, ok := s.omitEmptyFields[f]; ok {
|
|
continue
|
|
}
|
|
} else {
|
|
val = dereferencedValue(val)
|
|
}
|
|
data := val.Interface()
|
|
assignments = append(assignments, ub.Assign(quoted[i], data))
|
|
}
|
|
|
|
ub.Set(assignments...)
|
|
return ub
|
|
}
|
|
|
|
// InsertInto creates a new `InsertBuilder` with table name using verb INSERT INTO.
|
|
// By default, all exported fields of s are set as columns by calling `InsertBuilder#Cols`,
|
|
// and value is added as a list of values by calling `InsertBuilder#Values`.
|
|
//
|
|
// InsertInto never returns any error.
|
|
// If the type of any item in value is not expected, it will be ignored.
|
|
// If value is an empty slice, `InsertBuilder#Values` will not be called.
|
|
func (s *Struct) InsertInto(table string, value ...interface{}) *InsertBuilder {
|
|
return s.InsertIntoForTag(table, "", value...)
|
|
}
|
|
|
|
// InsertIgnoreInto creates a new `InsertBuilder` with table name using verb INSERT IGNORE INTO.
|
|
// By default, all exported fields of s are set as columns by calling `InsertBuilder#Cols`,
|
|
// and value is added as a list of values by calling `InsertBuilder#Values`.
|
|
//
|
|
// InsertIgnoreInto never returns any error.
|
|
// If the type of any item in value is not expected, it will be ignored.
|
|
// If value is an empty slice, `InsertBuilder#Values` will not be called.
|
|
func (s *Struct) InsertIgnoreInto(table string, value ...interface{}) *InsertBuilder {
|
|
return s.InsertIgnoreIntoForTag(table, "", value...)
|
|
}
|
|
|
|
// ReplaceInto creates a new `InsertBuilder` with table name using verb REPLACE INTO.
|
|
// By default, all exported fields of s are set as columns by calling `InsertBuilder#Cols`,
|
|
// and value is added as a list of values by calling `InsertBuilder#Values`.
|
|
//
|
|
// ReplaceInto never returns any error.
|
|
// If the type of any item in value is not expected, it will be ignored.
|
|
// If value is an empty slice, `InsertBuilder#Values` will not be called.
|
|
func (s *Struct) ReplaceInto(table string, value ...interface{}) *InsertBuilder {
|
|
return s.ReplaceIntoForTag(table, "", value...)
|
|
}
|
|
|
|
// buildColsAndValuesForTag uses ib to set exported fields tagged with tag as columns
|
|
// and add value as a list of values.
|
|
func (s *Struct) buildColsAndValuesForTag(ib *InsertBuilder, tag string, value ...interface{}) {
|
|
if s.taggedFields == nil {
|
|
return
|
|
}
|
|
|
|
fields, ok := s.taggedFields[tag]
|
|
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
vs := make([]reflect.Value, 0, len(value))
|
|
|
|
for _, item := range value {
|
|
v := reflect.ValueOf(item)
|
|
v = dereferencedValue(v)
|
|
|
|
if v.Type() == s.structType {
|
|
vs = append(vs, v)
|
|
}
|
|
}
|
|
|
|
if len(vs) == 0 {
|
|
return
|
|
}
|
|
cols := make([]string, 0, len(fields))
|
|
values := make([][]interface{}, len(vs))
|
|
|
|
for _, f := range fields {
|
|
cols = append(cols, f)
|
|
name := s.fieldAlias[f]
|
|
|
|
for i, v := range vs {
|
|
data := v.FieldByName(name).Interface()
|
|
values[i] = append(values[i], data)
|
|
}
|
|
}
|
|
|
|
cols = s.quoteFields(cols)
|
|
ib.Cols(cols...)
|
|
|
|
for _, value := range values {
|
|
ib.Values(value...)
|
|
}
|
|
}
|
|
|
|
// InsertIntoForTag creates a new `InsertBuilder` with table name using verb INSERT INTO.
|
|
// By default, exported fields tagged with tag are set as columns by calling `InsertBuilder#Cols`,
|
|
// and value is added as a list of values by calling `InsertBuilder#Values`.
|
|
//
|
|
// InsertIntoForTag never returns any error.
|
|
// If the type of any item in value is not expected, it will be ignored.
|
|
// If value is an empty slice, `InsertBuilder#Values` will not be called.
|
|
func (s *Struct) InsertIntoForTag(table string, tag string, value ...interface{}) *InsertBuilder {
|
|
ib := s.Flavor.NewInsertBuilder()
|
|
ib.InsertInto(table)
|
|
|
|
s.buildColsAndValuesForTag(ib, tag, value...)
|
|
return ib
|
|
}
|
|
|
|
// InsertIgnoreIntoForTag creates a new `InsertBuilder` with table name using verb INSERT IGNORE INTO.
|
|
// By default, exported fields tagged with tag are set as columns by calling `InsertBuilder#Cols`,
|
|
// and value is added as a list of values by calling `InsertBuilder#Values`.
|
|
//
|
|
// InsertIgnoreIntoForTag never returns any error.
|
|
// If the type of any item in value is not expected, it will be ignored.
|
|
// If value is an empty slice, `InsertBuilder#Values` will not be called.
|
|
func (s *Struct) InsertIgnoreIntoForTag(table string, tag string, value ...interface{}) *InsertBuilder {
|
|
ib := s.Flavor.NewInsertBuilder()
|
|
ib.InsertIgnoreInto(table)
|
|
|
|
s.buildColsAndValuesForTag(ib, tag, value...)
|
|
return ib
|
|
}
|
|
|
|
// ReplaceIntoForTag creates a new `InsertBuilder` with table name using verb REPLACE INTO.
|
|
// By default, exported fields tagged with tag are set as columns by calling `InsertBuilder#Cols`,
|
|
// and value is added as a list of values by calling `InsertBuilder#Values`.
|
|
//
|
|
// ReplaceIntoForTag never returns any error.
|
|
// If the type of any item in value is not expected, it will be ignored.
|
|
// If value is an empty slice, `InsertBuilder#Values` will not be called.
|
|
func (s *Struct) ReplaceIntoForTag(table string, tag string, value ...interface{}) *InsertBuilder {
|
|
ib := s.Flavor.NewInsertBuilder()
|
|
ib.ReplaceInto(table)
|
|
|
|
s.buildColsAndValuesForTag(ib, tag, value...)
|
|
return ib
|
|
}
|
|
|
|
// DeleteFrom creates a new `DeleteBuilder` with table name.
|
|
//
|
|
// Caller is responsible to set WHERE condition to match right record.
|
|
func (s *Struct) DeleteFrom(table string) *DeleteBuilder {
|
|
db := s.Flavor.NewDeleteBuilder()
|
|
db.DeleteFrom(table)
|
|
return db
|
|
}
|
|
|
|
// Addr takes address of all exported fields of the s from the value.
|
|
// The returned result can be used in `Row#Scan` directly.
|
|
func (s *Struct) Addr(value interface{}) []interface{} {
|
|
return s.AddrForTag("", value)
|
|
}
|
|
|
|
// AddrForTag takes address of all fields of the s tagged with tag from the value.
|
|
// The returned result can be used in `Row#Scan` directly.
|
|
//
|
|
// If tag is not defined in s in advance,
|
|
func (s *Struct) AddrForTag(tag string, value interface{}) []interface{} {
|
|
fields, ok := s.taggedFields[tag]
|
|
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
return s.AddrWithCols(fields, value)
|
|
}
|
|
|
|
// AddrWithCols takes address of all columns defined in cols from the value.
|
|
// The returned result can be used in `Row#Scan` directly.
|
|
func (s *Struct) AddrWithCols(cols []string, value interface{}) []interface{} {
|
|
v := reflect.ValueOf(value)
|
|
v = dereferencedValue(v)
|
|
|
|
if v.Type() != s.structType {
|
|
return nil
|
|
}
|
|
|
|
for _, c := range cols {
|
|
if _, ok := s.fieldAlias[c]; !ok {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
addrs := make([]interface{}, 0, len(cols))
|
|
|
|
for _, c := range cols {
|
|
name := s.fieldAlias[c]
|
|
data := v.FieldByName(name).Addr().Interface()
|
|
addrs = append(addrs, data)
|
|
}
|
|
|
|
return addrs
|
|
}
|
|
|
|
func (s *Struct) quoteFields(fields []string) []string {
|
|
// Try best not to allocate new slice.
|
|
if len(s.quotedFields) == 0 {
|
|
return fields
|
|
}
|
|
|
|
needQuote := false
|
|
|
|
for _, field := range fields {
|
|
if _, ok := s.quotedFields[field]; ok {
|
|
needQuote = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !needQuote {
|
|
return fields
|
|
}
|
|
|
|
quoted := make([]string, 0, len(fields))
|
|
|
|
for _, field := range fields {
|
|
if _, ok := s.quotedFields[field]; ok {
|
|
quoted = append(quoted, s.Flavor.Quote(field))
|
|
} else {
|
|
quoted = append(quoted, field)
|
|
}
|
|
}
|
|
|
|
return quoted
|
|
}
|
|
|
|
func dereferencedType(t reflect.Type) reflect.Type {
|
|
for k := t.Kind(); k == reflect.Ptr || k == reflect.Interface; k = t.Kind() {
|
|
t = t.Elem()
|
|
}
|
|
|
|
return t
|
|
}
|
|
|
|
func dereferencedValue(v reflect.Value) reflect.Value {
|
|
for k := v.Kind(); k == reflect.Ptr || k == reflect.Interface; k = v.Kind() {
|
|
v = v.Elem()
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
func isEmptyValue(value reflect.Value) bool {
|
|
switch value.Kind() {
|
|
case reflect.Interface, reflect.Ptr, reflect.Chan, reflect.Func, reflect.Map, reflect.Slice:
|
|
return value.IsNil()
|
|
case reflect.Bool:
|
|
return !value.Bool()
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return value.Int() == 0
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
return value.Uint() == 0
|
|
case reflect.String:
|
|
return value.String() == ""
|
|
case reflect.Float32, reflect.Float64:
|
|
return value.Float() == 0
|
|
}
|
|
|
|
return false
|
|
}
|