From 1eba9095496795a4c10c09ff906c1b9dada5ab84 Mon Sep 17 00:00:00 2001 From: awitas Date: Sat, 3 Jun 2023 18:52:12 -0700 Subject: [PATCH] moved setMarker to separate project --- differ_test.go | 66 ++++++++++++++++++++++++++++++++++++++++++++------ field.go | 3 +++ go.mod | 10 +++++--- index.go | 28 +++++++++++++++++++-- presence.go | 57 ------------------------------------------- struct.go | 46 ++++++++++++++++------------------- tag.go | 14 +++++------ 7 files changed, 120 insertions(+), 104 deletions(-) delete mode 100644 presence.go diff --git a/differ_test.go b/differ_test.go index 578c98b..cfd3528 100644 --- a/differ_test.go +++ b/differ_test.go @@ -11,6 +11,16 @@ import ( func TestNewDiffer(t *testing.T) { + type XEntry struct { + ID int + Name string + } + + type Holder struct { + ID int + Entries []*XEntry `diff:"indexBy=ID"` + } + type PresRecordHas struct { ID bool Name bool @@ -23,7 +33,7 @@ func TestNewDiffer(t *testing.T) { ScratchPad string `diff:"-"` Time time.Time Bar int - Has *PresRecordHas `diff:"presence=true"` + Has *PresRecordHas `setMarker:"true"` } type Record struct { @@ -225,6 +235,16 @@ func TestNewDiffer(t *testing.T) { {Type: "create", Path: &Path{Kind: PathKindIndex, Path: &Path{Kind: PathKindKey, Path: &Path{Kind: PathKinField, Path: &Path{}, Name: "ExprList"}, Key: "k1"}, Index: 0}, To: "0"}, }}, }, + + { + description: "repeated - update - shallow", + from: &Repeated{ID: 1, Records: []*Record{{ID: 12}}, Nums: []int{10, 2}}, + to: &Repeated{ID: 2, Records: []*Record{{ID: 23}}}, + options: []Option{WithShallow(true)}, + expect: &ChangeLog{Changes: []*Change{ + {Type: "update", Path: &Path{Kind: 1, Path: &Path{}, Name: "ID"}, From: 1, To: 2}, + }}, + }, {description: "diff with field presence check", from: &PresRecord{ID: 20, Name: "abc", Bar: 23, Has: &PresRecordHas{ @@ -240,21 +260,51 @@ func TestNewDiffer(t *testing.T) { options: []Option{WithPresence(true)}, expect: &ChangeLog{Changes: []*Change{ + {Type: "update", Path: &Path{Kind: PathKinField, Path: &Path{}, Name: "ID"}, From: 20, To: 21}, {Type: "update", Path: &Path{Kind: PathKinField, Path: &Path{}, Name: "Name"}, From: "abc", To: "xyz"}, }}, }, - { - description: "repeated - update - shallow", - from: &Repeated{ID: 1, Records: []*Record{{ID: 12}}, Nums: []int{10, 2}}, - to: &Repeated{ID: 2, Records: []*Record{{ID: 23}}}, - options: []Option{WithShallow(true)}, + description: "index by filed", + from: &Holder{ + ID: 1, + Entries: []*XEntry{ + { + ID: 1, + Name: "Name 1", + }, + { + ID: 2, + Name: "Name 2", + }, + }, + }, + to: &Holder{ + ID: 1, + Entries: []*XEntry{ + { + ID: 2, + Name: "Name 2", + }, + { + ID: 1, + Name: "Name 1", + }, + { + ID: 3, + Name: "Name 3", + }, + }, + }, expect: &ChangeLog{Changes: []*Change{ - {Type: "update", Path: &Path{Kind: 1, Path: &Path{}, Name: "ID"}, From: 1, To: 2}, + + {Type: "create", Path: &Path{Kind: PathKindIndex, Index: 2, Path: &Path{Kind: PathKinField, Path: &Path{}, Name: "Entries"}}, + From: (interface{})(nil), + To: &XEntry{ID: 3, Name: "Name 3"}}, }}, }, } - for _, testCase := range testCases { + for _, testCase := range testCases[len(testCases)-1:] { differ, err := New(reflect.TypeOf(testCase.from), reflect.TypeOf(testCase.to), testCase.configOptions...) if !assert.Nil(t, err, testCase.description) { continue diff --git a/field.go b/field.go index 8d00bf7..37d02a0 100644 --- a/field.go +++ b/field.go @@ -1,6 +1,7 @@ package godiff import ( + "github.com/viant/structology" "github.com/viant/xunsafe" "reflect" "strings" @@ -50,6 +51,8 @@ func (m *matcher) build(xStruct *xunsafe.Struct, config *Config) { xField := &xStruct.Fields[i] tag, _ := ParseTag(string(xField.Tag)) tag.init(config) + + tag.PresenceMarker = structology.IsSetMarker(xField.Tag) fieldAccessor := newAccessor(i, xField, tag) m.index[xField.Name] = &fieldAccessor m.index[strings.ToLower(xField.Name)] = &fieldAccessor diff --git a/go.mod b/go.mod index 1d77013..9bc9308 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,15 @@ module github.com/viant/godiff go 1.17 require ( - github.com/stretchr/testify v1.7.0 + github.com/stretchr/testify v1.8.4 github.com/viant/parsly v0.1.0 - github.com/viant/xunsafe v0.8.1 + github.com/viant/structology v0.1.0 + github.com/viant/xunsafe v0.8.4 ) require ( - github.com/davecgh/go-spew v1.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect + github.com/viant/xreflect v0.0.0-20230303201326-f50afb0feb0d // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/index.go b/index.go index 5531aef..986e958 100644 --- a/index.go +++ b/index.go @@ -1,7 +1,9 @@ package godiff import ( + "fmt" "github.com/viant/xunsafe" + "reflect" "unsafe" ) @@ -10,13 +12,35 @@ type entry struct { value interface{} } -type indexer struct{} +type indexer struct { + field *xunsafe.Field +} func (i *indexer) indexBy(xSlice *xunsafe.Slice, ptr unsafe.Pointer, by string) map[interface{}]*entry { if by == "" || by == "." { return i.indexPrimitive(xSlice, ptr) } - panic("not yet supported") + elemType := xSlice.Type.Elem() + if elemType.Kind() == reflect.Ptr { + elemType = elemType.Elem() + } + if elemType.Kind() != reflect.Struct { + panic(fmt.Sprintf("%s not yet supported", elemType.String())) + } + i.field = xunsafe.FieldByName(elemType, by) + return i.indexByField(xSlice, ptr) +} + +func (i *indexer) indexByField(xSlice *xunsafe.Slice, ptr unsafe.Pointer) map[interface{}]*entry { + var result = make(map[interface{}]*entry) + l := xSlice.Len(ptr) + for j := 0; j < l; j++ { + value := xSlice.ValueAt(ptr, j) + ptr := xunsafe.AsPointer(value) + key := i.field.Value(ptr) + result[key] = &entry{index: j, value: value} + } + return result } func (i *indexer) indexPrimitive(xSlice *xunsafe.Slice, ptr unsafe.Pointer) map[interface{}]*entry { diff --git a/presence.go b/presence.go deleted file mode 100644 index e150450..0000000 --- a/presence.go +++ /dev/null @@ -1,57 +0,0 @@ -package godiff - -import ( - "fmt" - "github.com/viant/xunsafe" - "reflect" - "unsafe" -) - -type PresenceProvider struct { - Holder *xunsafe.Field - Fields []*xunsafe.Field - IdentityIndex int -} - -//Has returns true if value on holder field with index has been set -func (p *PresenceProvider) Has(ptr unsafe.Pointer, index int) bool { - hasPtr := p.Holder.ValuePointer(ptr) - if index >= len(p.Fields) || p.Fields[index] == nil { - return false - } - return p.Fields[index].Bool(hasPtr) -} - -//IsFieldSet returns true if field has been set -func (p *PresenceProvider) IsFieldSet(ptr unsafe.Pointer, index int) bool { - if p == nil || p.Holder == nil { - return true //we do not have field presence provider so we assume all fields are set - } - if p.Holder.IsNil(ptr) { - return true //holder is nil - } - return p.Has(ptr, index) -} - -func (p *PresenceProvider) Init(filedPos map[string]int) error { - if p.Holder == nil || len(filedPos) == 0 { - return nil - } - if holder := p.Holder; holder != nil { - p.Fields = make([]*xunsafe.Field, len(filedPos)) - holderType := holder.Type - if holderType.Kind() == reflect.Ptr { - holderType = holderType.Elem() - } - for i := 0; i < holderType.NumField(); i++ { - presentField := holderType.Field(i) - pos, ok := filedPos[presentField.Name] - if !ok { - return fmt.Errorf("failed to match presence field %v %v", presentField.Name, filedPos) - } - p.Fields[pos] = xunsafe.NewField(presentField) - } - } - - return nil -} diff --git a/struct.go b/struct.go index 1af4791..0341a03 100644 --- a/struct.go +++ b/struct.go @@ -2,19 +2,20 @@ package godiff import ( "fmt" + "github.com/viant/structology" "github.com/viant/xunsafe" "reflect" ) type ( structDiffer struct { - config *Config - from *xunsafe.Struct - to *xunsafe.Struct - fromType reflect.Type - toType reflect.Type - fields []*field - presenceProvider PresenceProvider + config *Config + from *xunsafe.Struct + to *xunsafe.Struct + fromType reflect.Type + toType reflect.Type + fields []*field + marker structology.Marker } ) @@ -35,15 +36,13 @@ func (s *structDiffer) diff(changeLog *ChangeLog, path *Path, from, to interface } if options.presence { hasPtr := toPtr - if s.presenceProvider.Holder != nil { - if s.presenceProvider.Holder.IsNil(toPtr) { - hasPtr = fromPtr - } + if s.marker.CanUseHolder(toPtr) { + hasPtr = fromPtr } - if !s.presenceProvider.IsFieldSet(hasPtr, int(field.to.Index)) { + if !s.marker.IsSet(hasPtr, int(field.to.Index)) { continue //skip diff, to/dest is not set } - if field.tag != nil && field.tag.Presence { + if field.tag != nil && field.tag.PresenceMarker { continue //do not compare presence tag } } @@ -65,7 +64,7 @@ func (s *structDiffer) diff(changeLog *ChangeLog, path *Path, from, to interface } } if fromValue == nil { - + diffChangeType = ChangeTypeCreate } if err = field.differ.diff(changeLog, path.Field(field.name), fromValue, toValue, diffChangeType, options); err != nil { return err @@ -100,18 +99,17 @@ func (s *structDiffer) matchFields() error { matcher.build(s.to, s.config) } - var filedPos = map[string]int{} + marker, err := structology.NewMarker(s.fromType) + if err == nil { + s.marker = *marker + } for i := range s.from.Fields { fromField := &s.from.Fields[i] - filedPos[fromField.Name] = int(fromField.Index) tag, err := ParseTag(fromField.Tag.Get(s.config.TagName)) if err != nil { return err } - if tag.Presence { - s.presenceProvider.Holder = fromField - continue - } + if tag.Ignore { continue } @@ -137,6 +135,7 @@ func (s *structDiffer) matchFields() error { } aField.differ = &Differ{config: s.config, mapDiffer: differ} case reflect.Struct: + if aField.from.Type == s.fromType { aField.differ = &Differ{config: s.config, structDiffer: s} continue @@ -165,15 +164,12 @@ func (s *structDiffer) matchFields() error { } } } - if s.presenceProvider.Holder != nil { - s.presenceProvider.Init(filedPos) - } s.fields = fields return nil } func newStructDiffer(from, to reflect.Type, config *Config) (*structDiffer, error) { - var result = structDiffer{config: config, fromType: from, toType: timeType} + var result = structDiffer{config: config, fromType: from, toType: to} fromType := structType(from) if fromType == nil { @@ -183,6 +179,7 @@ func newStructDiffer(from, to reflect.Type, config *Config) (*structDiffer, erro if toType == nil { return nil, fmt.Errorf("invalid 'to' struct type: %s", to.String()) } + result.from = xunsafe.NewStruct(fromType) result.to = result.from if toType != fromType { @@ -191,6 +188,5 @@ func newStructDiffer(from, to reflect.Type, config *Config) (*structDiffer, erro if err := result.matchFields(); err != nil { return nil, err } - return &result, nil } diff --git a/tag.go b/tag.go index 1d1ae14..f145096 100644 --- a/tag.go +++ b/tag.go @@ -8,12 +8,12 @@ import ( //Tag represents a tag type Tag struct { - Name string - Presence bool - PairSeparator string - PairDelimiter string - pairDelimiter []string - ItemSeparator string + Name string + PresenceMarker bool + PairSeparator string + PairDelimiter string + pairDelimiter []string + ItemSeparator string Whitespace string IndexBy string @@ -76,8 +76,6 @@ func ParseTag(tagString string) (*Tag, error) { switch len(nv) { case 2: switch strings.ToLower(strings.TrimSpace(nv[0])) { - case "presence": - tag.Presence = true case "ignore": tag.Ignore = true case "name":