mirror of
https://github.com/hanzoai/sqlbuilder.git
synced 2026-10-01 00:37:42 +00:00
refs #60. more flexibility to filter fields with WithTag and WithoutTag in Struct
This commit is contained in:
@@ -33,3 +33,7 @@ debug_test
|
||||
|
||||
# Mac
|
||||
.DS_Store
|
||||
|
||||
# go work
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user