123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475 |
- // 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
- }
|