add a new fieldopt "omitempty"

This commit is contained in:
Huan Du
2019-03-20 00:18:57 +08:00
parent 5e6c0741f7
commit 015f032529
3 changed files with 118 additions and 39 deletions
+1
View File
@@ -75,6 +75,7 @@ type ATable struct {
Ignored int32 `db:"-"` // If we set field name as "-", Struct will ignore it.
unexported int // Unexported field is not visible to Struct.
Quoted string `db:"quoted" fieldopt:"withquote"` // Add quote to the field using back quote or double quote. See `Flavor#Quote`.
Empty uint `db:"empty" fieldopt:"omitempty"` // Omit the field in UPDATE if it is a nil or zero value.
}
```
+47 -12
View File
@@ -23,6 +23,7 @@ var (
const (
fieldOptWithQuote = "withquote"
fieldOptOmitEmpty = "omitempty"
)
// Struct represents a struct type.
@@ -32,10 +33,11 @@ const (
type Struct struct {
Flavor Flavor
structType reflect.Type
fieldAlias map[string]string
taggedFields map[string][]string
quotedFields map[string]struct{}
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
@@ -56,6 +58,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.parse(t)
return s
}
@@ -113,6 +116,8 @@ func (s *Struct) parse(t reflect.Type) {
switch opt {
case fieldOptWithQuote:
s.quotedFields[alias] = struct{}{}
case fieldOptOmitEmpty:
s.omitEmptyFields[alias] = struct{}{}
}
}
}
@@ -178,18 +183,26 @@ func (s *Struct) UpdateForTag(table string, tag string, value interface{}) *Upda
return ub
}
quoted := s.quoteFields(fields)
v := dereferencedValue(value)
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]
data := v.FieldByName(name).Interface()
val := v.FieldByName(name)
if _, ok := s.omitEmptyFields[f]; ok && isEmptyValue(val) {
continue
}
val = dereferencedValue(val)
data := val.Interface()
assignments = append(assignments, ub.Assign(quoted[i], data))
}
@@ -226,11 +239,14 @@ func (s *Struct) InsertIntoForTag(table string, tag string, value ...interface{}
vs := make([]reflect.Value, 0, len(value))
for _, item := range value {
v := dereferencedValue(item)
v := reflect.ValueOf(item)
v = dereferencedValue(v)
if v.Type() == s.structType {
vs = append(vs, v)
}
}
if len(vs) == 0 {
return ib
}
@@ -241,6 +257,7 @@ func (s *Struct) InsertIntoForTag(table string, tag string, value ...interface{}
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)
@@ -289,7 +306,8 @@ func (s *Struct) AddrForTag(tag string, value interface{}) []interface{} {
// 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 := dereferencedValue(value)
v := reflect.ValueOf(value)
v = dereferencedValue(v)
if v.Type() != s.structType {
return nil
@@ -352,12 +370,29 @@ func dereferencedType(t reflect.Type) reflect.Type {
return t
}
func dereferencedValue(value interface{}) reflect.Value {
v := reflect.ValueOf(value)
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
}
+70 -27
View File
@@ -238,15 +238,15 @@ type User struct {
}
type Order struct {
ID int64 `db:"id" fieldtag:"new"`
State State `db:"state" fieldtag:"new,paid,done"`
SkuID int64 `db:"sku_id" fieldtag:"new"`
UserID int64 `db:"user_id" fieldtag:"new"`
Price int64 `db:"price" fieldtag:"new,update"`
Discount int64 `db:"discount" fieldtag:"new,update"`
Description string `db:"description" fieldtag:"new,update"`
CreatedAt int64 `db:"created_at" fieldtag:"new"`
ModifiedAt int64 `db:"modified_at" fieldtag:"new,update,paid,done"`
ID int64 `db:"id" fieldtag:"new"`
State State `db:"state" fieldtag:"new,paid,done"`
SkuID int64 `db:"sku_id" fieldtag:"new"`
UserID int64 `db:"user_id" fieldtag:"new"`
Price int64 `db:"price" fieldtag:"new,update"`
Discount int64 `db:"discount" fieldtag:"new,update"`
Desc string `db:"desc" fieldtag:"new,update" fieldopt:"withquote"`
CreatedAt int64 `db:"created_at" fieldtag:"new"`
ModifiedAt int64 `db:"modified_at" fieldtag:"new,update,paid,done"`
}
type State int
@@ -318,15 +318,15 @@ func ExampleStruct_useTag() {
// Suppose we defined following type and global variable.
//
// type Order struct {
// ID int64 `db:"id" fieldtag:"update,paid"`
// State int `db:"state" fieldtag:"paid"`
// SkuID int64 `db:"sku_id"`
// UserID int64 `db:"user_id"`
// Price int64 `db:"price" fieldtag:"update"`
// Discount int64 `db:"discount" fieldtag:"update"`
// Description string `db:"description" fieldtag:"update"`
// CreatedAt int64 `db:"created_at"`
// ModifiedAt int64 `db:"modified_at" fieldtag:"update,paid"`
// ID int64 `db:"id" fieldtag:"update,paid"`
// State int `db:"state" fieldtag:"paid"`
// SkuID int64 `db:"sku_id"`
// UserID int64 `db:"user_id"`
// Price int64 `db:"price" fieldtag:"update"`
// Discount int64 `db:"discount" fieldtag:"update"`
// Desc string `db:"desc" fieldtag:"update" fieldopt:"withquote"` // `desc` is a keyword.
// CreatedAt int64 `db:"created_at"`
// ModifiedAt int64 `db:"modified_at" fieldtag:"update,paid"`
// }
//
// var orderStruct = NewStruct(new(Order))
@@ -334,19 +334,20 @@ func ExampleStruct_useTag() {
createOrder := func(table string) {
now := time.Now().Unix()
order := &Order{
ID: 1234,
State: OrderStateCreated,
SkuID: 5678,
UserID: 7527,
Price: 1000,
Discount: 0,
Description: "Best goods",
CreatedAt: now,
ModifiedAt: now,
ID: 1234,
State: OrderStateCreated,
SkuID: 5678,
UserID: 7527,
Price: 1000,
Discount: 0,
Desc: "Best goods",
CreatedAt: now,
ModifiedAt: now,
}
b := orderStruct.InsertInto(table, &order)
sql, args := b.Build()
db.Exec(sql, args)
fmt.Println(sql)
}
updatePrice := func(table string) {
tag := "update"
@@ -368,6 +369,7 @@ func ExampleStruct_useTag() {
b.Where(b.E("id", order.ID))
sql, args = b.Build()
db.Exec(sql, args...)
fmt.Println(sql)
}
updateState := func(table string) {
tag := "paid"
@@ -395,6 +397,7 @@ func ExampleStruct_useTag() {
b.Where(b.E("id", order.ID))
sql, args = b.Build()
db.Exec(sql, args...)
fmt.Println(sql)
}
table := "order"
@@ -405,6 +408,8 @@ func ExampleStruct_useTag() {
fmt.Println("done")
// Output:
// INSERT INTO order (id, state, sku_id, user_id, price, discount, `desc`, created_at, modified_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
// UPDATE order SET price = ?, discount = ?, `desc` = ?, modified_at = ? WHERE id = ?
// done
}
@@ -567,3 +572,41 @@ func TestStructWithQuote(t *testing.T) {
t.Fatalf("invalid sql. [expected:%v] [actual:%v]", expected, sql)
}
}
type structOmitEmpty struct {
A int `db:"aa" fieldopt:"omitempty,withquote"`
B *string `db:"bb" fieldopt:"omitempty"`
C uint16 `db:"cc" fieldopt:",omitempty"`
D *float64 `fieldopt:"omitempty"`
E bool `db:"ee"`
}
func TestStructOmitEmpty(t *testing.T) {
st := NewStruct(new(structOmitEmpty)).For(MySQL)
sql1, _ := st.Update("foo", new(structOmitEmpty)).Build()
if expected := "UPDATE foo SET ee = ?"; sql1 != expected {
t.Fatalf("invalid sql. [expected:%v] [actual:%v]", expected, sql1)
}
a := 123
b := "bbbb"
c := uint16(234)
d := 123.45
e := true
sql2, args2 := st.Update("foo", &structOmitEmpty{
A: a,
B: &b,
C: c,
D: &d,
E: e,
}).Build()
if expected := "UPDATE foo SET `aa` = ?, bb = ?, cc = ?, D = ?, ee = ?"; sql2 != expected {
t.Fatalf("invalid sql. [expected:%v] [actual:%v]", expected, sql2)
}
if expected := []interface{}{a, b, c, d, e}; !reflect.DeepEqual(expected, args2) {
t.Fatalf("invalid args. [expected:%#v] [actual:%#v]", expected, args2)
}
}