Merge pull request #50 from tolbier/omitempty_for_patch

Omitempty for TAG
This commit is contained in:
Huan Du
2020-11-02 10:44:00 +08:00
committed by GitHub
2 changed files with 135 additions and 10 deletions
+62 -10
View File
@@ -6,6 +6,7 @@ package sqlbuilder
import (
"bytes"
"reflect"
"regexp"
"strings"
)
@@ -25,8 +26,13 @@ var (
const (
fieldOptWithQuote = "withquote"
fieldOptOmitEmpty = "omitempty"
optName = "optName"
optParams = "optParams"
)
var optRegex = regexp.MustCompile(`(?P<` + optName + `>\w+)(\((?P<` + optParams + `>.*)\))?`)
// Struct represents a struct type.
//
// All methods in Struct are thread-safe.
@@ -38,7 +44,17 @@ type Struct struct {
fieldAlias map[string]string
taggedFields map[string][]string
quotedFields map[string]struct{}
omitEmptyFields map[string]struct{}
omitEmptyFields map[string]omitEmptyTagMap
}
type omitEmptyTagMap map[string]struct{}
func (sm omitEmptyTagMap) containsAny(tags ...string) (res bool) {
for _, tag := range tags {
if _, res = sm[tag]; res {
return
}
}
return
}
// NewStruct analyzes type information in structValue
@@ -59,7 +75,7 @@ func NewStruct(structValue interface{}) *Struct {
s.fieldAlias = map[string]string{}
s.taggedFields = map[string][]string{}
s.quotedFields = map[string]struct{}{}
s.omitEmptyFields = map[string]struct{}{}
s.omitEmptyFields = map[string]omitEmptyTagMap{}
s.parse(t)
return s
}
@@ -99,7 +115,7 @@ func (s *Struct) parse(t reflect.Type) {
// Parse FieldTag.
fieldtag := field.Tag.Get(FieldTag)
tags := strings.Split(fieldtag, ",")
tags := splitTokens(fieldtag)
for _, t := range tags {
if t != "" {
@@ -111,19 +127,29 @@ func (s *Struct) parse(t reflect.Type) {
// Parse FieldOpt.
fieldopt := field.Tag.Get(FieldOpt)
opts := strings.Split(fieldopt, ",")
opts := optRegex.FindAllString(fieldopt, -1)
for _, opt := range opts {
switch opt {
optMap := getOptMatchedMap(opt)
switch optMap[optName] {
case fieldOptOmitEmpty:
tags := getTagsFromOptParams(optMap[optParams])
s.appendOmitEmptyFieldsTags(alias, tags...)
case fieldOptWithQuote:
s.quotedFields[alias] = struct{}{}
case fieldOptOmitEmpty:
s.omitEmptyFields[alias] = struct{}{}
}
}
}
}
func (s *Struct) appendOmitEmptyFieldsTags(alias string, tags ...string) {
if s.omitEmptyFields[alias] == nil {
s.omitEmptyFields[alias] = omitEmptyTagMap{}
}
for _, tag := range tags {
s.omitEmptyFields[alias][tag] = struct{}{}
}
}
// SelectFrom creates a new `SelectBuilder` with table name.
// By default, all exported fields of the s are listed as columns in SELECT.
//
@@ -211,8 +237,10 @@ func (s *Struct) UpdateForTag(table string, tag string, value interface{}) *Upda
val := v.FieldByName(name)
if isEmptyValue(val) {
if _, ok := s.omitEmptyFields[f]; ok {
continue
if omitEmptyTagMap, ok := s.omitEmptyFields[f]; ok {
if omitEmptyTagMap.containsAny("", tag) {
continue
}
}
} else {
val = dereferencedValue(val)
@@ -439,6 +467,30 @@ func (s *Struct) quoteFields(fields []string) []string {
return quoted
}
func getOptMatchedMap(opt string) (res map[string]string) {
res = map[string]string{}
sm := optRegex.FindStringSubmatch(opt)
for i, name := range optRegex.SubexpNames() {
if name != "" {
res[name] = sm[i]
}
}
return
}
func getTagsFromOptParams(opts string) (tags []string) {
tags = splitTokens(opts)
if len(tags) == 0 {
tags = append(tags, "")
}
return
}
func splitTokens(fieldtag string) (res []string) {
res = strings.Split(fieldtag, ",")
for i, v := range res {
res[i] = strings.TrimSpace(v)
}
return
}
func dereferencedType(t reflect.Type) reflect.Type {
for k := t.Kind(); k == reflect.Ptr || k == reflect.Interface; k = t.Kind() {
t = t.Elem()
+73
View File
@@ -644,6 +644,79 @@ func TestStructOmitEmpty(t *testing.T) {
}
}
type structOmitEmptyForTag struct {
A int `db:"aa" fieldopt:"omitempty,withquote" fieldtag:"patch"`
B *string `db:"bb" fieldopt:"omitempty" fieldtag:"patch"`
C uint16 `db:"cc" fieldopt:"omitempty()" fieldtag:"patch"`
D *float64 `fieldopt:"omitempty(patch)" fieldtag:"patch"`
E bool `db:"ee" fieldtag:"patch"`
}
func TestStructOmitEmptyForTag(t *testing.T) {
st := NewStruct(new(structOmitEmptyForTag)).For(MySQL)
sql1, _ := st.Update("foo", new(structOmitEmptyForTag)).Build()
if expected := "UPDATE foo SET D = ?, ee = ?"; sql1 != expected {
t.Fatalf("invalid sql. [expected:%v] [actual:%v]", expected, sql1)
}
a := 123
b := "bbbb"
c := uint16(234)
e := true
sql2, args2 := st.UpdateForTag("foo", "patch", &structOmitEmptyForTag{
A: a,
B: &b,
C: c,
D: nil,
E: e,
}).Build()
if expected := "UPDATE foo SET `aa` = ?, bb = ?, cc = ?, ee = ?"; sql2 != expected {
t.Fatalf("invalid sql. [expected:%v] [actual:%v]", expected, sql2)
}
if expected := []interface{}{a, b, c, e}; !reflect.DeepEqual(expected, args2) {
t.Fatalf("invalid args. [expected:%#v] [actual:%#v]", expected, args2)
}
}
type structOmitEmptyForMultipleTags struct {
A int `db:"aa" fieldopt:"omitempty, omitempty(patch, patch2),withquote" fieldtag:"patch, patch2 "`
B *string `db:"bb" fieldopt:"omitempty" fieldtag:"patch"`
C uint16 `db:"cc" fieldopt:"omitempty, omitempty(patch2)" fieldtag:"patch2"`
D *float64 `fieldopt:"omitempty(patch, patch2)" fieldtag:"patch,patch2"`
E bool `db:"ee" fieldtag:"patch"`
}
func TestStructOmitEmptyForMultipleTags(t *testing.T) {
st := NewStruct(new(structOmitEmptyForMultipleTags)).For(MySQL)
sql1, _ := st.Update("foo", new(structOmitEmptyForMultipleTags)).Build()
if expected := "UPDATE foo SET D = ?, ee = ?"; sql1 != expected {
t.Fatalf("invalid sql. [expected:%v] [actual:%v]", expected, sql1)
}
a := 123
b := "bbbb"
e := true
sql2, args2 := st.UpdateForTag("foo", "patch2", &structOmitEmptyForMultipleTags{
A: a,
B: &b,
C: 0,
D: nil,
E: e,
}).Build()
if expected := "UPDATE foo SET `aa` = ?"; sql2 != expected {
t.Fatalf("invalid sql. [expected:%v] [actual:%v]", expected, sql2)
}
if expected := []interface{}{a}; !reflect.DeepEqual(expected, args2) {
t.Fatalf("invalid args. [expected:%#v] [actual:%#v]", expected, args2)
}
}
type structWithPointers struct {
A int `db:"aa" fieldopt:"omitempty"`
B *string `db:"bb"`