refs #60. more flexibility to filter fields with WithTag and WithoutTag in Struct

This commit is contained in:
Huan Du
2023-02-27 00:59:49 +08:00
parent 59650fe906
commit 42130d3b3a
5 changed files with 438 additions and 86 deletions
+4
View File
@@ -33,3 +33,7 @@ debug_test
# Mac
.DS_Store
# go work
go.work
go.work.sum
+1 -1
View File
@@ -136,7 +136,7 @@ We can define a struct type and use field tags to let `Struct` know how to creat
type ATable struct {
Field1 string // If a field doesn't has a tag, use "Field1" as column name in SQL.
Field2 int `db:"field2"` // Use "db" in field tag to set column name used in SQL.
Field3 int64 `db:"field3" fieldtag:"foo,bar"` // Set fieldtag to a field. We can call `WithTag` to change default tag or call methods like `Struct#SelectForTag` to set tag explicitly.
Field3 int64 `db:"field3" fieldtag:"foo,bar"` // Set fieldtag to a field. We can call `WithTag` to include fields with tag or `WithoutTag` to exclude fields with tag.
Field4 int64 `db:"field4" fieldtag:"foo"` // If we use `s.WithTag("foo").Select(table)`, columnes of SELECT are field3 and field4.
Field5 string `db:"field5" fieldas:"f5_alias"` // Use "fieldas" in field tag to set a column alias (AS) used in SELECT.
Ignored int32 `db:"-"` // If we set field name as "-", Struct will ignore it.
+257 -26
View File
@@ -9,6 +9,7 @@ import (
"math"
"reflect"
"regexp"
"sort"
)
var (
@@ -48,7 +49,8 @@ type Struct struct {
structType reflect.Type
structFieldsParser structFieldsParser
structTag string
withTags []string
withoutTags []string
}
var emptyStruct Struct
@@ -91,29 +93,209 @@ func (s *Struct) WithFieldMapper(mapper FieldMapperFunc) *Struct {
return &c
}
// WithTag sets default tag for all builder methods.
// For instance, calling s.WithTag("tag").SelectFrom("t") is the same as calling s.SelectFromForTag("t", "tag").
func (s *Struct) WithTag(tag string) *Struct {
// WithTag sets included tag(s) for all builder methods.
// For instance, calling s.WithTag("tag").SelectFrom("t") is to select all fields tagged with "tag" from table "t".
//
// If multiple tags are provided, fields tagged with any of them are included.
// That is, s.WithTag("tag1", "tag2").SelectFrom("t") is to select all fields tagged with "tag1" or "tag2" from table "t".
func (s *Struct) WithTag(tags ...string) *Struct {
if len(tags) == 0 {
return s
}
c := *s
c.structTag = tag
c.mergeWithTags(tags)
return &c
}
func (s *Struct) mergeWithTags(with []string) {
newTags := make([]int, 0, len(with))
withTags := s.withTags
withoutTags := s.withoutTags
if len(withoutTags) == 0 {
for i, tag := range with {
if tag == "" {
continue
}
if !hasTag(withTags, tag) {
newTags = append(newTags, i)
}
}
} else {
for i, tag := range with {
if tag == "" {
continue
}
if !hasTag(withTags, tag) {
if !hasTag(withoutTags, tag) {
newTags = append(newTags, i)
}
}
}
}
if len(newTags) == 0 {
return
}
// Merge with tags.
withTags = make([]string, 0, len(s.withTags)+len(newTags))
withTags = append(withTags, s.withTags...)
for _, idx := range newTags {
withTags = append(withTags, with[idx])
}
sort.Strings(withTags)
withTags = removeDuplicatedTags(withTags)
s.withTags = withTags
}
// WithoutTag sets excluded tag(s) for all builder methods.
// For instance, calling s.WithoutTag("tag").SelectFrom("t") is to select all fields except those tagged with "tag" from table "t".
//
// If multiple tags are provided, fields tagged with any of them are excluded.
// That is, s.WithoutTag("tag1", "tag2").SelectFrom("t") is to exclude any field tagged with "tag1" or "tag2" from table "t".
func (s *Struct) WithoutTag(tags ...string) *Struct {
if len(tags) == 0 {
return s
}
c := *s
c.mergeWithoutTags(tags)
return &c
}
func (s *Struct) mergeWithoutTags(without []string) {
withTags := s.withTags
withoutTags := s.withoutTags
if len(withoutTags) == 0 {
withoutTags = make([]string, len(without))
copy(withoutTags, without)
} else {
newTags := make([]int, 0, len(without))
for i, tag := range without {
if tag == "" {
continue
}
if !hasTag(withoutTags, tag) {
newTags = append(newTags, i)
}
}
if len(newTags) == 0 {
return
}
// Merge without tags.
tags := make([]string, 0, len(withoutTags)+len(newTags))
tags = append(tags, withoutTags...)
for _, idx := range newTags {
tags = append(tags, without[idx])
}
withoutTags = tags
}
sort.Strings(withoutTags)
withoutTags = removeDuplicatedTags(withoutTags)
// Filter out useless tags in s.withTags.
kept := make([]int, 0, len(withTags))
for i, tag := range withTags {
if !hasTag(withoutTags, tag) {
kept = append(kept, i)
}
}
if len(kept) > 0 {
filteredTags := make([]string, 0, len(kept))
for _, i := range kept {
filteredTags = append(filteredTags, withTags[i])
}
withTags = filteredTags
} else {
withTags = nil
}
// Update with and without tags.
s.withTags = withTags
s.withoutTags = withoutTags
}
func hasTag(tags []string, tag string) bool {
if len(tags) == 0 {
return false
}
i := sort.SearchStrings(tags, tag)
return i < len(tags) && tags[i] == tag
}
func removeDuplicatedTags(tags []string) []string {
if len(tags) <= 1 {
return tags
}
// Unlikely to find any duplicates.
hasDupes := false
for i := 1; i < len(tags); i++ {
if tags[i] == tags[i-1] {
hasDupes = true
break
}
}
if !hasDupes {
return tags
}
unique := make([]string, 0, len(tags))
unique = append(unique, tags[0])
for i := 1; i < len(tags); i++ {
if tags[i] != tags[i-1] {
unique = append(unique, tags[i])
}
}
return unique
}
// 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, s.structTag)
return s.selectFromWithTags(table, s.withTags, s.withoutTags)
}
// 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.
//
// Deprecated: It's recommended to use s.WithTag(tag).SelectFrom(...) instead of calling this method.
// The former one is more readable and can be chained with other methods.
func (s *Struct) SelectFromForTag(table string, tag string) (sb *SelectBuilder) {
return s.selectFromWithTags(table, []string{tag}, nil)
}
func (s *Struct) selectFromWithTags(table string, with, without []string) (sb *SelectBuilder) {
sfs := s.structFieldsParser()
tagged := sfs.Tag(tag)
tagged := sfs.FilterTags(with, without)
sb = s.Flavor.NewSelectBuilder()
sb.From(table)
@@ -147,7 +329,7 @@ func (s *Struct) SelectFromForTag(table string, tag string) (sb *SelectBuilder)
//
// Caller is responsible to set WHERE condition to match right record.
func (s *Struct) Update(table string, value interface{}) *UpdateBuilder {
return s.UpdateForTag(table, s.structTag, value)
return s.updateWithTags(table, s.withTags, s.withoutTags, value)
}
// UpdateForTag creates a new `UpdateBuilder` with table name.
@@ -155,9 +337,16 @@ func (s *Struct) Update(table string, value interface{}) *UpdateBuilder {
// 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.
//
// Deprecated: It's recommended to use s.WithTag(tag).Update(...) instead of calling this method.
// The former one is more readable and can be chained with other methods.
func (s *Struct) UpdateForTag(table string, tag string, value interface{}) *UpdateBuilder {
return s.updateWithTags(table, []string{tag}, nil, value)
}
func (s *Struct) updateWithTags(table string, with, without []string, value interface{}) *UpdateBuilder {
sfs := s.structFieldsParser()
tagged := sfs.Tag(tag)
tagged := sfs.FilterTags(with, without)
ub := s.Flavor.NewUpdateBuilder()
ub.Update(table)
@@ -180,7 +369,7 @@ func (s *Struct) UpdateForTag(table string, tag string, value interface{}) *Upda
val := v.FieldByName(name)
if isEmptyValue(val) {
if sf.ShouldOmitEmpty("", tag) {
if sf.ShouldOmitEmpty(with...) {
continue
}
} else {
@@ -203,7 +392,11 @@ func (s *Struct) UpdateForTag(table string, tag string, value interface{}) *Upda
// 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, s.structTag, value...)
ib := s.Flavor.NewInsertBuilder()
ib.InsertInto(table)
s.buildColsAndValuesForTag(ib, s.withTags, s.withoutTags, value...)
return ib
}
// InsertIgnoreInto creates a new `InsertBuilder` with table name using verb INSERT IGNORE INTO.
@@ -214,7 +407,11 @@ func (s *Struct) InsertInto(table string, value ...interface{}) *InsertBuilder {
// 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, s.structTag, value...)
ib := s.Flavor.NewInsertBuilder()
ib.InsertIgnoreInto(table)
s.buildColsAndValuesForTag(ib, s.withTags, s.withoutTags, value...)
return ib
}
// ReplaceInto creates a new `InsertBuilder` with table name using verb REPLACE INTO.
@@ -225,14 +422,18 @@ func (s *Struct) InsertIgnoreInto(table string, value ...interface{}) *InsertBui
// 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, s.structTag, value...)
ib := s.Flavor.NewInsertBuilder()
ib.ReplaceInto(table)
s.buildColsAndValuesForTag(ib, s.withTags, s.withoutTags, value...)
return ib
}
// 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{}) {
func (s *Struct) buildColsAndValuesForTag(ib *InsertBuilder, with, without []string, value ...interface{}) {
sfs := s.structFieldsParser()
tagged := sfs.Tag(tag)
tagged := sfs.FilterTags(with, without)
if tagged == nil {
return
@@ -260,7 +461,7 @@ func (s *Struct) buildColsAndValuesForTag(ib *InsertBuilder, tag string, value .
for _, sf := range tagged.ForWrite {
cols = append(cols, sf.Quote(s.Flavor))
name := sf.Name
shouldOmitEmpty := sf.ShouldOmitEmpty("", tag)
shouldOmitEmpty := sf.ShouldOmitEmpty(with...)
nilCnt := 0
for i, v := range vs {
@@ -313,11 +514,14 @@ func (s *Struct) buildColsAndValuesForTag(ib *InsertBuilder, tag string, value .
// 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.
//
// Deprecated: It's recommended to use s.WithTag(tag).InsertInto(...) instead of calling this method.
// The former one is more readable and can be chained with other methods.
func (s *Struct) InsertIntoForTag(table string, tag string, value ...interface{}) *InsertBuilder {
ib := s.Flavor.NewInsertBuilder()
ib.InsertInto(table)
s.buildColsAndValuesForTag(ib, tag, value...)
s.buildColsAndValuesForTag(ib, []string{tag}, nil, value...)
return ib
}
@@ -328,11 +532,14 @@ func (s *Struct) InsertIntoForTag(table string, tag string, value ...interface{}
// 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.
//
// Deprecated: It's recommended to use s.WithTag(tag).InsertIgnoreInto(...) instead of calling this method.
// The former one is more readable and can be chained with other methods.
func (s *Struct) InsertIgnoreIntoForTag(table string, tag string, value ...interface{}) *InsertBuilder {
ib := s.Flavor.NewInsertBuilder()
ib.InsertIgnoreInto(table)
s.buildColsAndValuesForTag(ib, tag, value...)
s.buildColsAndValuesForTag(ib, []string{tag}, nil, value...)
return ib
}
@@ -343,11 +550,14 @@ func (s *Struct) InsertIgnoreIntoForTag(table string, tag string, value ...inter
// 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.
//
// Deprecated: It's recommended to use s.WithTag(tag).ReplaceInto(...) instead of calling this method.
// The former one is more readable and can be chained with other methods.
func (s *Struct) ReplaceIntoForTag(table string, tag string, value ...interface{}) *InsertBuilder {
ib := s.Flavor.NewInsertBuilder()
ib.ReplaceInto(table)
s.buildColsAndValuesForTag(ib, tag, value...)
s.buildColsAndValuesForTag(ib, []string{tag}, nil, value...)
return ib
}
@@ -363,16 +573,23 @@ func (s *Struct) DeleteFrom(table string) *DeleteBuilder {
// Addr takes address of all exported fields of the s from the st.
// The returned result can be used in `Row#Scan` directly.
func (s *Struct) Addr(st interface{}) []interface{} {
return s.AddrForTag(s.structTag, st)
return s.addrWithTags(s.withTags, s.withoutTags, st)
}
// AddrForTag takes address of all fields of the s tagged with tag from the st.
// The returned value can be used in `Row#Scan` directly.
//
// If tag is not defined in s in advance, returns nil.
//
// Deprecated: It's recommended to use s.WithTag(tag).Addr(...) instead of calling this method.
// The former one is more readable and can be chained with other methods.
func (s *Struct) AddrForTag(tag string, st interface{}) []interface{} {
return s.addrWithTags([]string{tag}, nil, st)
}
func (s *Struct) addrWithTags(with, without []string, st interface{}) []interface{} {
sfs := s.structFieldsParser()
tagged := sfs.Tag(tag)
tagged := sfs.FilterTags(with, without)
if tagged == nil {
return nil
@@ -385,7 +602,7 @@ func (s *Struct) AddrForTag(tag string, st interface{}) []interface{} {
// The returned value can be used in `Row#Scan` directly.
func (s *Struct) AddrWithCols(cols []string, st interface{}) []interface{} {
sfs := s.structFieldsParser()
tagged := sfs.Tag(s.structTag)
tagged := sfs.FilterTags(s.withTags, s.withoutTags)
if tagged == nil {
return nil
@@ -421,13 +638,20 @@ func (s *Struct) addrWithFields(fields []*structField, st interface{}) []interfa
// Columns returns column names of s for all exported struct fields.
func (s *Struct) Columns() []string {
return s.ColumnsForTag(s.structTag)
return s.columnsWithTags(s.withTags, s.withoutTags)
}
// ColumnsForTag returns column names of the s tagged with tag.
//
// Deprecated: It's recommended to use s.WithTag(tag).Columns(...) instead of calling this method.
// The former one is more readable and can be chained with other methods.
func (s *Struct) ColumnsForTag(tag string) (cols []string) {
return s.columnsWithTags([]string{tag}, nil)
}
func (s *Struct) columnsWithTags(with, without []string) (cols []string) {
sfs := s.structFieldsParser()
tagged := sfs.Tag(tag)
tagged := sfs.FilterTags(with, without)
if tagged == nil {
return
@@ -444,13 +668,20 @@ func (s *Struct) ColumnsForTag(tag string) (cols []string) {
// Values returns a shadow copy of all exported fields in st.
func (s *Struct) Values(st interface{}) []interface{} {
return s.ValuesForTag(s.structTag, st)
return s.valuesWithTags(s.withTags, s.withoutTags, st)
}
// ValuesForTag returns a shadow copy of all fields tagged with tag in st.
//
// Deprecated: It's recommended to use s.WithTag(tag).Values(...) instead of calling this method.
// The former one is more readable and can be chained with other methods.
func (s *Struct) ValuesForTag(tag string, value interface{}) (values []interface{}) {
return s.valuesWithTags([]string{tag}, nil, value)
}
func (s *Struct) valuesWithTags(with, without []string, value interface{}) (values []interface{}) {
sfs := s.structFieldsParser()
tagged := sfs.Tag(tag)
tagged := sfs.FilterTags(with, without)
if tagged == nil {
return
+129 -55
View File
@@ -259,33 +259,61 @@ func TestStructColumns(t *testing.T) {
a.Equal(userForTest.ColumnsForTag("invalid"), nil)
}
func TestWithAndWithoutTags(t *testing.T) {
type Tags struct {
A int `db:"a" fieldtag:"tag1"`
B int `db:"b" fieldtag:"tag2"`
C int `db:"c" fieldtag:"tag3"`
D int `db:"d" fieldtag:"tag1,tag2"`
E int `db:"e" fieldtag:"tag2,tag3"`
F int `db:"f" fieldtag:"tag1,tag3"`
G int `db:"g" fieldtag:"tag1,tag2,tag3"`
H int `db:"h"`
}
structTags := NewStruct(Tags{})
a := assert.New(t)
a.Equal(structTags.Columns(), []string{"a", "b", "c", "d", "e", "f", "g", "h"})
a.Equal(structTags.WithTag("tag1").Columns(), []string{"a", "d", "f", "g"})
a.Equal(structTags.WithTag("tag2").Columns(), []string{"b", "d", "e", "g"})
a.Equal(structTags.WithTag("tag3").Columns(), []string{"c", "e", "f", "g"})
a.Equal(structTags.WithTag("tag1", "tag2").Columns(), []string{"a", "d", "f", "g", "b", "e"})
a.Equal(structTags.WithTag("tag1", "tag3").Columns(), []string{"a", "d", "f", "g", "c", "e"})
a.Equal(structTags.WithTag("tag2", "tag3").Columns(), []string{"b", "d", "e", "g", "c", "f"})
a.Equal(structTags.WithTag("tag2", "tag3", "tag2", "tag3").Columns(), []string{"b", "d", "e", "g", "c", "f"})
a.Equal(structTags.WithoutTag("tag3").Columns(), []string{"a", "b", "d", "h"})
a.Equal(structTags.WithoutTag("tag3", "tag2").Columns(), []string{"a", "h"})
a.Equal(structTags.WithoutTag("tag3", "tag2", "tag3", "tag2").Columns(), []string{"a", "h"})
a.Equal(structTags.WithTag("tag1", "tag2").WithoutTag("tag3").Columns(), []string{"a", "d", "b"})
a.Equal(structTags.WithoutTag("tag3").WithTag("tag1", "tag2").Columns(), []string{"a", "d", "b"})
a.Equal(structTags.WithTag("tag1", "tag2", "tag3").WithoutTag("tag3").Columns(), []string{"a", "d", "b"})
a.Equal(structTags.WithoutTag("tag3", "tag1").WithTag("tag1", "tag2", "tag3").Columns(), []string{"b"})
a.Equal(structTags.WithTag("tag2").WithTag("tag1").Columns(), []string{"a", "d", "f", "g", "b", "e"})
a.Equal(structTags.WithoutTag("tag3").WithTag("tag1").WithTag("tag3", "tag2").Columns(), []string{"a", "d", "b"})
a.Equal(structTags.WithoutTag("tag3").WithTag("tag1").WithTag("tag3", "tag2").WithoutTag("tag1", "tag3").Columns(), []string{"b"})
}
type User struct {
ID int64 `db:"id"`
Name string `db:"name"`
Status int `db:"status"`
}
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"`
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
type testDB int
type testRows int
func (db testDB) Query(string, ...interface{}) (testRows, error) {
return 0, nil
func (db *testDB) Query(string, ...interface{}) (testRows, error) {
rows := testRows(*db)
*db++
return rows, nil
}
func (db testDB) Exec(query string, args ...interface{}) {
func (db *testDB) Exec(query string, args ...interface{}) {
}
func (rows testRows) Close() error {
@@ -293,13 +321,21 @@ func (rows testRows) Close() error {
}
func (rows testRows) Scan(dest ...interface{}) error {
_, _ = fmt.Sscan("1234 huandu 1", dest...)
if rows == 0 {
fmt.Sscan("1234 huandu 1", dest...)
} else if rows == 1 {
fmt.Sscan("1234 34 huandu 1456725903636000000", dest...)
} else if rows == 2 {
fmt.Sscan("1 1456725903636000000", dest...)
} else {
panic("invalid rows")
}
return nil
}
var userStruct = NewStruct(new(User))
var orderStruct = NewStruct(new(Order))
var db testDB
var userDB testDB = 0
const (
OrderStateInvalid State = iota
@@ -324,7 +360,7 @@ func ExampleStruct_useStructAsORM() {
// Execute the query.
sql, args := sb.Build()
rows, _ := db.Query(sql, args...)
rows, _ := userDB.Query(sql, args...)
defer func(rows testRows) {
_ = rows.Close()
}(rows)
@@ -343,23 +379,26 @@ func ExampleStruct_useStructAsORM() {
// sqlbuilder.User{ID:1234, Name:"huandu", Status:1}
}
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"`
// 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))
var orderDB testDB = 1
func ExampleStruct_WithTag() {
// Suppose we defined following type for an order.
type Order struct {
ID int64 `db:"id"`
State State `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:"new,update" fieldopt:"withquote"`
CreatedAt int64 `db:"created_at"`
ModifiedAt int64 `db:"modified_at" fieldtag:"update,paid"`
}
// The orderStruct is a global variable for Order type.
var orderStruct = NewStruct(new(Order))
// Create an order with all fields set.
createOrder := func(table string) {
now := time.Now().Unix()
order := &Order{
@@ -375,20 +414,24 @@ func ExampleStruct_useTag() {
}
b := orderStruct.InsertInto(table, &order)
sql, args := b.Build()
db.Exec(sql, args)
orderDB.Exec(sql, args)
fmt.Println(sql)
}
// Update order only with price related fields, which is tagged with "update".
updatePrice := func(table string) {
tag := "update"
// Use tag "update" in all struct methods.
st := orderStruct.WithTag("update")
// Read order from database.
var order Order
sql, args := orderStruct.SelectFromForTag(table, tag).Where("id = 1234").Build()
rows, _ := db.Query(sql, args...)
sql, args := st.SelectFrom(table).Where("id = 1234").Build()
rows, _ := orderDB.Query(sql, args...)
defer func(rows testRows) {
_ = rows.Close()
}(rows)
_ = rows.Scan(orderStruct.AddrForTag(tag, &order)...)
_ = rows.Scan(st.Addr(&order)...)
fmt.Println(sql)
// Discount for this user.
// Use tag "update" to update necessary columns only.
@@ -396,29 +439,33 @@ func ExampleStruct_useTag() {
order.ModifiedAt = time.Now().Unix()
// Save the order.
b := orderStruct.UpdateForTag(table, tag, &order)
b := st.Update(table, &order)
b.Where(b.E("id", order.ID))
sql, args = b.Build()
db.Exec(sql, args...)
orderDB.Exec(sql, args...)
fmt.Println(sql)
}
// Update order only with payment related fields, which is tagged with "paid".
updateState := func(table string) {
tag := "paid"
st := orderStruct.WithTag("paid")
// Read order from database.
var order Order
sql, args := orderStruct.SelectFromForTag(table, tag).Where("id = 1234").Build()
rows, _ := db.Query(sql, args...)
sql, args := st.SelectFrom(table).Where("id = 1234").Build()
rows, _ := orderDB.Query(sql, args...)
defer func(rows testRows) {
_ = rows.Close()
}(rows)
_ = rows.Scan(orderStruct.AddrForTag(tag, &order)...)
_ = rows.Scan(st.Addr(&order)...)
fmt.Println(sql)
// Update state to paid when user has paid for the order.
// Use tag "paid" to update necessary columns only.
if order.State != OrderStateCreated {
// Report state error here.
return
panic(order.State)
// return
}
// Update order state.
@@ -426,10 +473,10 @@ func ExampleStruct_useTag() {
order.ModifiedAt = time.Now().Unix()
// Save the order.
b := orderStruct.UpdateForTag(table, tag, &order)
b := st.Update(table, &order)
b.Where(b.E("id", order.ID))
sql, args = b.Build()
db.Exec(sql, args...)
orderDB.Exec(sql, args...)
fmt.Println(sql)
}
@@ -438,12 +485,39 @@ func ExampleStruct_useTag() {
updatePrice(table)
updateState(table)
fmt.Println("done")
// Output:
// INSERT INTO order (id, state, sku_id, user_id, price, discount, `desc`, created_at, modified_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
// SELECT order.price, order.discount, order.`desc`, order.modified_at FROM order WHERE id = 1234
// UPDATE order SET price = ?, discount = ?, `desc` = ?, modified_at = ? WHERE id = ?
// done
// SELECT order.state, order.modified_at FROM order WHERE id = 1234
// UPDATE order SET state = ?, modified_at = ? WHERE id = ?
}
func ExampleStruct_WithoutTag() {
// We can use WithoutTag to exclude fields with specific tag.
// It's useful when we want to update all fields except some fields.
type User struct {
ID int64 `db:"id" fieldtag:"pk"`
FirstName string `db:"first_name"`
LastName string `db:"last_name"`
ModifiedAtTime time.Time `db:"modified_at_time"`
}
// The userStruct is a global variable for User type.
var userStruct = NewStruct(new(User))
// Update user with all fields except the user_id field which is tagged with "pk".
user := &User{
FirstName: "Huan",
LastName: "Du",
ModifiedAtTime: time.Now(),
}
sql, _ := userStruct.WithoutTag("pk").Update("user", user).Where("id = 1234").Build()
fmt.Println(sql)
// Output:
// UPDATE user SET first_name = ?, last_name = ?, modified_at_time = ? WHERE id = 1234
}
func ExampleStruct_buildUPDATE() {
@@ -468,7 +542,7 @@ func ExampleStruct_buildUPDATE() {
// Execute the query.
sql, args := ub.Build()
db.Exec(sql, args...)
orderDB.Exec(sql, args...)
fmt.Println(sql)
fmt.Println(args)
@@ -499,7 +573,7 @@ func ExampleStruct_buildINSERT() {
// Execute the query.
sql, args := ib.Build()
db.Exec(sql, args...)
orderDB.Exec(sql, args...)
fmt.Println(sql)
fmt.Println(args)
@@ -531,7 +605,7 @@ func ExampleStruct_buildDELETE() {
// Execute the query.
sql, args := b.Build()
db.Exec(sql, args...)
orderDB.Exec(sql, args...)
fmt.Println(sql)
fmt.Println(args)
+47 -4
View File
@@ -154,13 +154,51 @@ func (sfs *structFields) parse(t reflect.Type, mapper FieldMapperFunc, prefix st
}
}
// Tag returns the fields with tag.
func (sfs *structFields) Tag(tag string) *structTaggedFields {
if tag == "" {
func (sfs *structFields) FilterTags(with, without []string) *structTaggedFields {
if len(with) == 0 && len(without) == 0 {
return sfs.noTag
}
return sfs.tagged[tag]
// Simply return the tagged fields.
if len(with) == 1 && len(without) == 0 {
return sfs.tagged[with[0]]
}
// Find out all with and without fields.
taggedFields := makeStructTaggedFields()
filteredReadFields := make(map[string]struct{}, len(sfs.noTag.colsForRead))
for _, tag := range without {
if field, ok := sfs.tagged[tag]; ok {
for k := range field.colsForRead {
filteredReadFields[k] = struct{}{}
}
}
}
if len(with) == 0 {
for _, field := range sfs.noTag.ForRead {
k := field.Key()
if _, ok := filteredReadFields[k]; !ok {
taggedFields.Add(field)
}
}
} else {
for _, tag := range with {
if fields, ok := sfs.tagged[tag]; ok {
for _, field := range fields.ForRead {
k := field.Key()
if _, ok := filteredReadFields[k]; !ok {
taggedFields.Add(field)
}
}
}
}
}
return taggedFields
}
func (sfs *structFields) taggedFields(tag string) *structTaggedFields {
@@ -256,6 +294,11 @@ func (sf *structField) ShouldOmitEmpty(tags ...string) (ret bool) {
return
}
// Always check default tag.
if _, ret = omit[""]; ret {
return
}
for _, tag := range tags {
if _, ret = omit[tag]; ret {
return