From 0a3a9aacf695b5d3653eb07ba9a57fcb39b8cbc1 Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Thu, 14 Nov 2019 12:07:08 -0500 Subject: [PATCH 01/27] added decode load map --- decoder.go | 63 +++++++++++++++++++++++++++++++++++++++++++++---- decoder_test.go | 38 ++++++++++++++++------------- defaults.go | 14 +++++++++++ delete_test.go | 2 +- save_test.go | 16 ++++++------- util_test.go | 2 +- 6 files changed, 103 insertions(+), 32 deletions(-) diff --git a/decoder.go b/decoder.go index c15e608..c7d7f6d 100644 --- a/decoder.go +++ b/decoder.go @@ -81,19 +81,20 @@ func decode(rawArr [][]interface{}, respObj interface{}) (err error) { } } nodeLookup := make(map[int64]*reflect.Value) + relMaps := make(map[int64]map[string]*RelationLoad) var pks []int64 rels := make(map[int64]*neoEdgeConfig) labelLookup := map[int64]string{} if paths != nil && len(paths) != 0 { - err = sortPaths(paths, &nodeLookup, &rels, &pks, primaryLabel) + err = sortPaths(paths, &nodeLookup, &rels, &pks, primaryLabel, &relMaps) if err != nil { return err } } if isolatedNodes != nil && len(isolatedNodes) != 0 { - err = sortIsolatedNodes(isolatedNodes, &labelLookup, &nodeLookup, &pks, primaryLabel) + err = sortIsolatedNodes(isolatedNodes, &labelLookup, &nodeLookup, &pks, primaryLabel, &relMaps) if err != nil { return err } @@ -113,7 +114,6 @@ func decode(rawArr [][]interface{}, respObj interface{}) (err error) { //build relationships for _, relationConfig := range rels { - //todo figure out why this is broken if relationConfig.StartNodeType == "" || relationConfig.EndNodeType == "" { continue } @@ -136,6 +136,50 @@ func decode(rawArr [][]interface{}, respObj interface{}) (err error) { return err } + if startMap, ok := relMaps[relationConfig.StartNodeId]; ok { + if conf, ok := startMap[startConfig.FieldName]; ok { + conf.Ids = append(conf.Ids, relationConfig.EndNodeId) + } else { + var rt RelationType + if startConfig.ManyRelationship { + rt = Multi + } else { + rt = Single + } + + newConf := &RelationLoad{ + Ids: []int64{relationConfig.EndNodeId}, + RelationType: rt, + } + + startMap[startConfig.FieldName] = newConf + } + } else { + return fmt.Errorf("relation config not found for id [%v]", relationConfig.StartNodeId) + } + + if endMap, ok := relMaps[relationConfig.EndNodeId]; ok { + if conf, ok := endMap[endConfig.FieldName]; ok { + conf.Ids = append(conf.Ids, relationConfig.EndNodeId) + } else { + var rt RelationType + if endConfig.ManyRelationship { + rt = Multi + } else { + rt = Single + } + + newConf := &RelationLoad{ + Ids: []int64{relationConfig.StartNodeId}, + RelationType: rt, + } + + endMap[endConfig.FieldName] = newConf + } + } else { + return fmt.Errorf("relation config not found for id [%v]", relationConfig.StartNodeId) + } + if startConfig.UsesEdgeNode { var typeConfig structDecoratorConfig @@ -231,6 +275,13 @@ func decode(rawArr [][]interface{}, respObj interface{}) (err error) { } } + //set load maps + if len(rels) != 0 { + for id, val := range nodeLookup { + reflect.Indirect(*val).FieldByName(loadMapField).Set(reflect.ValueOf(relMaps[id])) + } + } + //handle if its returning a slice -- validation has been done at an earlier step if rt.Elem().Kind() == reflect.Slice { @@ -284,7 +335,7 @@ func getPrimaryLabel(rt reflect.Type) string { return rt.Name() } -func sortIsolatedNodes(isolatedNodes []*graph.Node, labelLookup *map[int64]string, nodeLookup *map[int64]*reflect.Value, pks *[]int64, pkLabel string) error { +func sortIsolatedNodes(isolatedNodes []*graph.Node, labelLookup *map[int64]string, nodeLookup *map[int64]*reflect.Value, pks *[]int64, pkLabel string, relMaps *map[int64]map[string]*RelationLoad) error { if isolatedNodes == nil { return fmt.Errorf("isolatedNodes can not be nil, %w", ErrInternal) } @@ -303,6 +354,7 @@ func sortIsolatedNodes(isolatedNodes []*graph.Node, labelLookup *map[int64]strin } (*nodeLookup)[node.NodeIdentity] = val + (*relMaps)[node.NodeIdentity] = map[string]*RelationLoad{} //primary to return if node.Labels != nil && len(node.Labels) != 0 && node.Labels[0] == pkLabel { @@ -355,7 +407,7 @@ func sortStrictRels(strictRels []*graph.Relationship, labelLookup *map[int64]str return nil } -func sortPaths(paths []*graph.Path, nodeLookup *map[int64]*reflect.Value, rels *map[int64]*neoEdgeConfig, pks *[]int64, pkLabel string) error { +func sortPaths(paths []*graph.Path, nodeLookup *map[int64]*reflect.Value, rels *map[int64]*neoEdgeConfig, pks *[]int64, pkLabel string, relMaps *map[int64]map[string]*RelationLoad) error { if paths == nil { return fmt.Errorf("paths is empty, that shouldn't have happened, %w", ErrInternal) } @@ -379,6 +431,7 @@ func sortPaths(paths []*graph.Path, nodeLookup *map[int64]*reflect.Value, rels * } (*nodeLookup)[node.NodeIdentity] = val + (*relMaps)[node.NodeIdentity] = map[string]*RelationLoad{} //primary to return if node.Labels != nil && len(node.Labels) != 0 && node.Labels[0] == pkLabel { diff --git a/decoder_test.go b/decoder_test.go index 11b8969..9edb6b0 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -158,13 +158,13 @@ type tdString string type tdInt int type f struct { - embedTest + BaseNode Parents []*f `gogm:"direction=outgoing;relationship=test"` Children []*f `gogm:"direction=incoming;relationship=test"` } type a struct { - embedTest + BaseNode TestField string `gogm:"name=test_field"` TestTypeDefString tdString `gogm:"name=test_type_def_string"` TestTypeDefInt tdInt `gogm:"name=test_type_def_int"` @@ -176,7 +176,7 @@ type a struct { } type b struct { - embedTest + BaseNode TestField string `gogm:"name=test_field"` TestTime time.Time `gogm:"time;name=test_time"` Single *a `gogm:"direction=outgoing;relationship=test_rel"` @@ -187,7 +187,7 @@ type b struct { } type c struct { - embedTest + BaseNode Start *a End *b Test string `gogm:"name=test"` @@ -284,21 +284,21 @@ func TestDecoder(t *testing.T) { } f0 := f{ - embedTest: embedTest{ + BaseNode: BaseNode{ Id: 0, UUID: "0", }, } f1 := f{ - embedTest: embedTest{ + BaseNode: BaseNode{ Id: 1, UUID: "1", }, } f2 := f{ - embedTest: embedTest{ + BaseNode: BaseNode{ Id: 2, UUID: "2", }, @@ -315,13 +315,17 @@ func TestDecoder(t *testing.T) { for _, r := range readin10 { if r.Id == 0 { req.True(len(r.Parents) == 1) + req.True(r.LoadMap["Parents"].Ids[0] == 1) req.True(len(r.Children) == 0) } else if r.Id == 1 { req.True(len(r.Parents) == 1) + req.True(r.LoadMap["Parents"].Ids[0] == 2) req.True(len(r.Children) == 1) + req.True(r.LoadMap["Children"].Ids[0] == 0) } else if r.Id == 2 { req.True(len(r.Parents) == 0) req.True(len(r.Children) == 1) + req.True(r.LoadMap["Children"].Ids[0] == 1) } else { t.FailNow() } @@ -366,7 +370,7 @@ func TestDecoder(t *testing.T) { var readin a comp := &a{ - embedTest: embedTest{ + BaseNode: BaseNode{ Id: 1, UUID: "dasdfasd", }, @@ -376,7 +380,7 @@ func TestDecoder(t *testing.T) { } comp22 := &b{ - embedTest: embedTest{ + BaseNode: BaseNode{ Id: 2, UUID: "dasdfas", }, @@ -455,7 +459,7 @@ func TestDecoder(t *testing.T) { var readin2 a comp2 := &a{ - embedTest: embedTest{ + BaseNode: BaseNode{ Id: 1, UUID: "dasdfasd", }, @@ -463,7 +467,7 @@ func TestDecoder(t *testing.T) { } b2 := &b{ - embedTest: embedTest{ + BaseNode: BaseNode{ Id: 2, UUID: "dasdfas", }, @@ -472,7 +476,7 @@ func TestDecoder(t *testing.T) { } c1 := &c{ - embedTest: embedTest{ + BaseNode: BaseNode{ Id: 34, UUID: "asdfasdafsd", }, @@ -529,7 +533,7 @@ func TestDecoder(t *testing.T) { var readin3 a comp3 := a{ - embedTest: embedTest{ + BaseNode: BaseNode{ Id: 1, UUID: "dasdfasd", }, @@ -537,7 +541,7 @@ func TestDecoder(t *testing.T) { MultiA: []*b{ { TestField: "test", - embedTest: embedTest{ + BaseNode: BaseNode{ Id: 2, UUID: "dasdfas", }, @@ -597,7 +601,7 @@ func TestDecoder(t *testing.T) { comp4 := &a{ TestField: "test", - embedTest: embedTest{ + BaseNode: BaseNode{ Id: 1, UUID: "dasdfasd", }, @@ -605,7 +609,7 @@ func TestDecoder(t *testing.T) { b3 := &b{ TestField: "test", - embedTest: embedTest{ + BaseNode: BaseNode{ Id: 2, UUID: "dasdfas", }, @@ -613,7 +617,7 @@ func TestDecoder(t *testing.T) { } c4 := c{ - embedTest: embedTest{ + BaseNode: BaseNode{ UUID: "asdfasdafsd", }, Start: comp4, diff --git a/defaults.go b/defaults.go index addd244..15f1a35 100644 --- a/defaults.go +++ b/defaults.go @@ -1,6 +1,20 @@ package gogm +const loadMapField = "LoadMap" + type BaseNode struct { Id int64 `json:"-" gogm:"name=id"` UUID string `json:"uuid" gogm:"pk;name=uuid"` + LoadMap map[string]*RelationLoad `json:"-" gogm:"-"` +} + +type RelationType int +const ( + Single RelationType = 0 + Multi RelationType = 1 +) + +type RelationLoad struct { + Ids []int64 `json:"-"` + RelationType RelationType `json:"-"` } \ No newline at end of file diff --git a/delete_test.go b/delete_test.go index 8ed8288..140a3bf 100644 --- a/delete_test.go +++ b/delete_test.go @@ -18,7 +18,7 @@ func TestDelete(t *testing.T) { defer driverPool.Reclaim(conn) del := a{ - embedTest: embedTest{ + BaseNode: BaseNode{ Id: 0, UUID: "5334ee8c-6231-40fd-83e5-16c8016ccde6", }, diff --git a/save_test.go b/save_test.go index 211e37b..d48d65a 100644 --- a/save_test.go +++ b/save_test.go @@ -26,13 +26,13 @@ func parseO2O(req *require.Assertions) { TestField: "test", TestTypeDefString: "dasdfas", TestTypeDefInt: 600, - embedTest: embedTest{ + BaseNode: BaseNode{ Id: 1, }, } b1 := &b{ - embedTest: embedTest{ + BaseNode: BaseNode{ Id: 2, }, TestField: "test", @@ -66,7 +66,7 @@ func parseM2O(req *require.Assertions) { TestField: "test", TestTypeDefString: "dasdfas", TestTypeDefInt: 600, - embedTest: embedTest{ + BaseNode: BaseNode{ Id: 1, }, ManyA: []*b{}, @@ -74,7 +74,7 @@ func parseM2O(req *require.Assertions) { b1 := &b{ TestField: "test", - embedTest: embedTest{ + BaseNode: BaseNode{ Id: 2, }, } @@ -101,7 +101,7 @@ func parseM2M(req *require.Assertions) { TestField: "test", TestTypeDefString: "dasdfas", TestTypeDefInt: 600, - embedTest: embedTest{ + BaseNode: BaseNode{ Id: 1, }, ManyA: []*b{}, @@ -110,7 +110,7 @@ func parseM2M(req *require.Assertions) { b1 := &b{ TestField: "test", - embedTest: embedTest{ + BaseNode: BaseNode{ Id: 2, }, Multi: []*a{}, @@ -140,7 +140,7 @@ func TestSave(t *testing.T) { comp2 := &a{ TestField: "test", - embedTest: embedTest{ + BaseNode: BaseNode{ Id: 1, }, } @@ -148,7 +148,7 @@ func TestSave(t *testing.T) { b2 := &b{ TestField: "test", TestTime: time.Now().UTC(), - embedTest: embedTest{ + BaseNode: BaseNode{ Id: 2, }, } diff --git a/util_test.go b/util_test.go index c51761f..6abaeb2 100644 --- a/util_test.go +++ b/util_test.go @@ -41,7 +41,7 @@ func TestGetTypeName(t *testing.T) { func TestToCypherParamsMap(t *testing.T) { val := a{ - embedTest: embedTest{ + BaseNode: BaseNode{ Id: 0, UUID: "testuuid", }, From b08112d19621957f71e93e38e7789e5ce165fab6 Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Fri, 15 Nov 2019 15:56:27 -0500 Subject: [PATCH 02/27] edge validation done #8 --- decoder.go | 4 +++ decoder_test.go | 2 +- errors.go | 1 + save.go | 85 ++++++++++++++++++++++++++++++++++++++++++++++--- save_test.go | 6 ++-- setup.go | 11 +++++-- util.go | 81 +++++++++++++++++++++++++++++++++++++++++++++- 7 files changed, 179 insertions(+), 11 deletions(-) diff --git a/decoder.go b/decoder.go index c7d7f6d..a4b3f84 100644 --- a/decoder.go +++ b/decoder.go @@ -546,6 +546,10 @@ func convertToValue(graphId int64, conf structDecoratorConfig, props map[string] continue } + if fieldConfig.Ignore { + continue + } + if fieldConfig.Properties { mapType := reflect.MapOf(reflect.TypeOf(""), emptyInterfaceType) mapVal := reflect.MakeMap(mapType) diff --git a/decoder_test.go b/decoder_test.go index 9edb6b0..330193e 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -180,7 +180,7 @@ type b struct { TestField string `gogm:"name=test_field"` TestTime time.Time `gogm:"time;name=test_time"` Single *a `gogm:"direction=outgoing;relationship=test_rel"` - ManyB *a `gogm:"direction=incoming;relationship=testm2o"` + ManyB *a `gogm:"direction=outgoing;relationship=testm2o"` Multi []*a `gogm:"direction=outgoing;relationship=multib"` SingleSpec *c `gogm:"direction=incoming;relationship=special_single"` MultiSpec []*c `gogm:"direction=incoming;relationship=special_multi"` diff --git a/errors.go b/errors.go index c2677c4..cbf835e 100644 --- a/errors.go +++ b/errors.go @@ -37,6 +37,7 @@ func (i *InvalidStructConfigError) Error() string { var ErrNotFound = errors.New("gogm: data not found") var ErrInternal = errors.New("gogm: internal error") +var ErrValidation = errors.New("gogm: struct validation error") var ErrInvalidParams = errors.New("gogm: invalid params") var ErrConfiguration = errors.New("gogm: configuration was malformed") var ErrTransaction = errors.New("gogm: transaction error") diff --git a/save.go b/save.go index 54fff41..b613194 100644 --- a/save.go +++ b/save.go @@ -24,6 +24,11 @@ type relCreateConf struct { Direction dsl.Direction } +type relDelConf struct { + StartNodeUUID string + EndNodeUUID string +} + func saveDepth(sess *driver.BoltConn, obj interface{}, depth int) error { if sess == nil { return errors.New("session can not be nil") @@ -61,9 +66,11 @@ func saveDepth(sess *driver.BoltConn, obj interface{}, depth int) error { //signature is [LABEL] []{config} relations := map[string][]relCreateConf{} + dels := []*relDelConf{} + rootVal := reflect.ValueOf(obj) - err := parseStruct("", "", false, 0, nil, &rootVal, 0, depth, &nodes, &relations) + err := parseStruct("", "", false, 0, nil, &rootVal, 0, depth, &nodes, &relations, dels) if err != nil { return err } @@ -78,9 +85,79 @@ func saveDepth(sess *driver.BoltConn, obj interface{}, depth int) error { return nil } + if len(dels) != 0 { + err = removeRelations(sess, dels) + if err != nil { + return err + } + } + return relateNodes(sess, relations, ids) } +func removeRelations(conn *driver.BoltConn, dels []*relDelConf) error { + if dels == nil || len(dels) == 0 { + return nil + } + + if conn == nil { + return fmt.Errorf("connection can not be nil, %w", ErrInternal) + } + + var params []interface{} + + for _, delConf := range dels { + params = append(params, map[string]interface{}{ + "startNodeId": delConf.StartNodeUUID, + "endNodeId": delConf.EndNodeUUID, + }) + } + + startParams, err := dsl.ParamsFromMap(map[string]interface{}{ + "uuid": dsl.ParamString("{row.startNodeId}"), + }) + if err != nil { + return fmt.Errorf("%s, %w", err.Error(), ErrInternal) + } + + endParams, err := dsl.ParamsFromMap(map[string]interface{}{ + "uuid": dsl.ParamString("{row.endNodeId}"), + }) + if err != nil { + return fmt.Errorf("%s, %w", err.Error(), ErrInternal) + } + + res, err := dsl.QB(). + Cypher("UNWIND {rows} as row"). + Match(dsl.Path(). + V(dsl.V{ + Name: "start", + Params: startParams, + }).E(dsl.E{ + Name: "e", + }).V(dsl.V{ + Name: "end", + Params: endParams, + }).Build()). + Delete(false, "e"). + WithNeo(conn). + Exec(map[string]interface{}{ + "rows": params, + }, + ) + if err != nil { + return fmt.Errorf("%s, %w", err.Error(), ErrInternal) + } + + if rows, err := res.RowsAffected(); err != nil { + return fmt.Errorf("%s, %w", err.Error(), ErrInternal) + } else if int(rows) != len(dels) { + return fmt.Errorf("sanity check failed, rows affected [%v] not equal to num deletions [%v], %w", rows, len(dels), ErrInternal) + } else { + return nil + } +} + func createNodes(conn *driver.BoltConn, crNodes map[string]map[string]nodeCreateConf) (map[string]int64, error) { idMap := map[string]int64{} @@ -274,7 +351,7 @@ func parseValidate(currentDepth, maxDepth int, current *reflect.Value, nodesPtr return nil } -func parseStruct(parentId, edgeLabel string, parentIsStart bool, direction dsl.Direction, edgeParams map[string]interface{}, current *reflect.Value, currentDepth int, maxDepth int, nodesPtr *map[string]map[string]nodeCreateConf, relationsPtr *map[string][]relCreateConf) error { +func parseStruct(parentId, edgeLabel string, parentIsStart bool, direction dsl.Direction, edgeParams map[string]interface{}, current *reflect.Value, currentDepth int, maxDepth int, nodesPtr *map[string]map[string]nodeCreateConf, relationsPtr *map[string][]relCreateConf, dels []*relDelConf) error { //check if its done if currentDepth > maxDepth { return nil @@ -393,7 +470,7 @@ func parseStruct(parentId, edgeLabel string, parentIsStart bool, direction dsl.D continue } - err = parseStruct(newParentId, newEdgeLabel, newParentIdStart, newDirection, newEdgeParams, followVal, currentDepth+1, maxDepth, nodesPtr, relationsPtr) + err = parseStruct(newParentId, newEdgeLabel, newParentIdStart, newDirection, newEdgeParams, followVal, currentDepth+1, maxDepth, nodesPtr, relationsPtr, dels) if err != nil { return err } @@ -408,7 +485,7 @@ func parseStruct(parentId, edgeLabel string, parentIsStart bool, direction dsl.D continue } - err = parseStruct(newParentId, newEdgeLabel, newParentIdStart, newDirection, newEdgeParams, followVal, currentDepth+1, maxDepth, nodesPtr, relationsPtr) + err = parseStruct(newParentId, newEdgeLabel, newParentIdStart, newDirection, newEdgeParams, followVal, currentDepth+1, maxDepth, nodesPtr, relationsPtr, dels) if err != nil { return err } diff --git a/save_test.go b/save_test.go index d48d65a..a7ec722 100644 --- a/save_test.go +++ b/save_test.go @@ -52,7 +52,7 @@ func parseO2O(req *require.Assertions) { val := reflect.ValueOf(comp1) - err := parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations) + err := parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations, nil) req.Nil(err) req.Equal(2, len(nodes)) req.Equal(1, len(nodes["a"])) @@ -87,7 +87,7 @@ func parseM2O(req *require.Assertions) { val := reflect.ValueOf(a1) - err := parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations) + err := parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations, nil) req.Nil(err) req.Equal(2, len(nodes)) req.Equal(1, len(nodes["a"])) @@ -124,7 +124,7 @@ func parseM2M(req *require.Assertions) { val := reflect.ValueOf(a1) - err := parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations) + err := parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations, nil) req.Nil(err) req.Equal(2, len(nodes)) req.Equal(1, len(nodes["a"])) diff --git a/setup.go b/setup.go index 4f447d2..ce4c605 100644 --- a/setup.go +++ b/setup.go @@ -88,7 +88,10 @@ func Init(conf *Config, mapTypes ...interface{}) error { func setupInit(isTest bool, conf *Config, mapTypes ...interface{}) error { if isSetup && !isTest { return errors.New("gogm has already been initialized") + } else if isTest && isSetup{ + mappedRelations = &relationConfigs{} } + if !isTest { if conf == nil { return errors.New("config can not be nil") @@ -103,12 +106,16 @@ func setupInit(isTest bool, conf *Config, mapTypes ...interface{}) error { return err } - log.Debugf("mapped type '%s'", name) - log.Infof("mapped type %s", name) mappedTypes.Set(name, *dc) } + log.Debug("validating edges") + if err := mappedRelations.Validate(); err != nil { + log.WithError(err).Error("failed to validate edges") + return err + } + if !isTest { log.Debug("opening connection to neo4j") diff --git a/util.go b/util.go index 39e5730..bc0784f 100644 --- a/util.go +++ b/util.go @@ -6,6 +6,7 @@ import ( "github.com/google/uuid" go_cypherdsl "github.com/mindstand/go-cypherdsl" "reflect" + "strings" "sync" "time" ) @@ -73,7 +74,7 @@ func toCypherParamsMap(val reflect.Value, config structDecoratorConfig) (map[str ret := map[string]interface{}{} for _, conf := range config.Fields { - if conf.Relationship != "" || conf.Name == "id" { + if conf.Relationship != "" || conf.Name == "id" || conf.Ignore { continue } @@ -206,6 +207,84 @@ func (r *relationConfigs) getConfig(nodeType, relationship, fieldType string, di } } +type validation struct { + Incoming []string + Outgoing []string + None []string + Both []string +} + +func (r *relationConfigs) Validate() error { + r.mutex.Lock() + defer r.mutex.Unlock() + + checkMap := map[string]*validation{} + + for title, confMap := range r.configs { + parts := strings.Split(title, "-") + if len(parts) != 2 { + return fmt.Errorf("invalid length for parts [%v] should be 2. Rel is [%s], %w", len(parts), title, ErrValidation) + } + + //vType := parts[0] + relType := parts[1] + + for field, configs := range confMap { + for _, config := range configs { + if _, ok := checkMap[relType]; !ok { + checkMap[relType] = &validation{ + Incoming: []string{}, + Outgoing: []string{}, + None: []string{}, + Both: []string{}, + } + } + + validate := checkMap[relType] + + switch config.Direction { + case go_cypherdsl.DirectionIncoming: + validate.Incoming = append(validate.Incoming, field) + break + case go_cypherdsl.DirectionOutgoing: + validate.Outgoing = append(validate.Outgoing, field) + break + case go_cypherdsl.DirectionNone: + validate.None = append(validate.None, field) + break + case go_cypherdsl.DirectionBoth: + validate.Both = append(validate.Both, field) + break + default: + return fmt.Errorf("unrecognized direction [%s], %w", config.Direction.ToString(), ErrValidation) + } + } + } + } + + for relType, validateConfig := range checkMap { + //check normal + if len(validateConfig.Outgoing) != len(validateConfig.Incoming){ + return fmt.Errorf("invalid directional configuration on relationship [%s], %w", relType, ErrValidation) + } + + //check both direction + if len(validateConfig.Both) != 0 { + if len(validateConfig.Both) % 2 != 0 { + return fmt.Errorf("invalid length for 'both' validation, %w", ErrValidation) + } + } + + //check none direction + if len(validateConfig.None) != 0 { + if len(validateConfig.None) % 2 != 0 { + return fmt.Errorf("invalid length for 'both' validation, %w", ErrValidation) + } + } + } + return nil +} + //isDifferentType, differentType, error func getActualTypeIfAliased(iType reflect.Type) (bool, reflect.Type, error) { if iType == nil { From 1843fec7132aa7a4b38e095b4ec73aad1be31258 Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Fri, 15 Nov 2019 17:59:58 -0500 Subject: [PATCH 03/27] started work on the cli --- cmd/gogm/gogm.go | 45 +++++++++++++++++++++++++ cmd/gogm/gogm_test.go | 2 ++ cmd/gogm/parse.go | 78 +++++++++++++++++++++++++++++++++++++++++++ testing_/test_obj.go | 10 ++++++ testing_/test_obj2.go | 10 ++++++ 5 files changed, 145 insertions(+) create mode 100644 cmd/gogm/gogm.go create mode 100644 cmd/gogm/gogm_test.go create mode 100644 cmd/gogm/parse.go create mode 100644 testing_/test_obj.go create mode 100644 testing_/test_obj2.go diff --git a/cmd/gogm/gogm.go b/cmd/gogm/gogm.go new file mode 100644 index 0000000..1ff843c --- /dev/null +++ b/cmd/gogm/gogm.go @@ -0,0 +1,45 @@ +package main + +import ( + "log" + "os" + "path/filepath" + "strings" +) + + + +func main() { + directory := "/home/erictg97/mindstand/repos/gogm/testing_" + + + confs := map[string][]*relConf{} + imps := map[string][]string{} + + err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error { + if info.IsDir() { + return nil + } + + if err != nil { + log.Println("failed here") + return err + } + + if strings.Contains(path, ".go") { + err := parseFile(path, &confs, imps) + if err != nil { + log.Fatal(err) + } + } + + return nil + }) + if err != nil { + log.Fatal(err) + } + + log.Println(confs) + log.Println(imps) +} + diff --git a/cmd/gogm/gogm_test.go b/cmd/gogm/gogm_test.go new file mode 100644 index 0000000..c9ecbf5 --- /dev/null +++ b/cmd/gogm/gogm_test.go @@ -0,0 +1,2 @@ +package main + diff --git a/cmd/gogm/parse.go b/cmd/gogm/parse.go new file mode 100644 index 0000000..6d7b53a --- /dev/null +++ b/cmd/gogm/parse.go @@ -0,0 +1,78 @@ +package main + +import ( + "bytes" + "go/ast" + "go/parser" + "go/printer" + "go/token" + "log" + "strings" +) + +type relConf struct { + Field string + Type string + IsMany bool +} + +func parseFile(filePath string, confs *map[string][]*relConf, imports map[string][]string) error { + fset := token.NewFileSet() + node, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments) + if err != nil { + log.Fatal(err) + } + + if node.Scope != nil { + if node.Scope.Objects != nil && len(node.Scope.Objects) != 0 { + for label, config := range node.Scope.Objects { + tSpec, ok := config.Decl.(*ast.TypeSpec) + if !ok { + continue + } + + strType, ok := tSpec.Type.(*ast.StructType) + if !ok { + continue + } + + if node.Imports != nil && len(node.Imports) != 0 { + var imps []string + + for _, impSpec := range node.Imports { + imps = append(imps, impSpec.Path.Value) + } + + imports[label] = imps + } + + (*confs)[label] = []*relConf{} + + if strType.Fields != nil && strType.Fields.List != nil && len(strType.Fields.List) != 0 { + for _, field := range strType.Fields.List { + if field.Tag != nil && field.Tag.Value != "" { + if strings.Contains(field.Tag.Value, "relationship") && strings.Contains(field.Tag.Value, "direction") { + var typeNameBuf bytes.Buffer + + err = printer.Fprint(&typeNameBuf, fset, field.Type) + if err != nil { + log.Fatal(err) + } + + t := typeNameBuf.String() + + (*confs)[label] = append((*confs)[label], &relConf{ + Field: field.Names[0].Name, + Type: t, + IsMany: strings.Contains(t, "[]"), + }) + } + } + } + } + } + } + } + + return nil +} \ No newline at end of file diff --git a/testing_/test_obj.go b/testing_/test_obj.go new file mode 100644 index 0000000..0efd1f7 --- /dev/null +++ b/testing_/test_obj.go @@ -0,0 +1,10 @@ +package testing_ + +import "github.com/mindstand/gogm" + +type ExampleObject struct { + gogm.BaseNode + + Children []*ExampleObject `gogm:"direction=incoming;relationship=test"` + Parents *ExampleObject `gogm:"direction=outgoing;relationship=test"` +} diff --git a/testing_/test_obj2.go b/testing_/test_obj2.go new file mode 100644 index 0000000..400a19e --- /dev/null +++ b/testing_/test_obj2.go @@ -0,0 +1,10 @@ +package testing_ + +import "github.com/mindstand/gogm" + +type ExampleObject2 struct { + gogm.BaseNode + + Children2 []*ExampleObject2 `gogm:"direction=incoming;relationship=test"` + Parents2 *ExampleObject2 `gogm:"direction=outgoing;relationship=test"` +} From 7ff2d91ac8dce1635c722c06e661485658210fb8 Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Wed, 27 Nov 2019 14:19:10 -0500 Subject: [PATCH 04/27] more progress on the parser --- cmd/gogm/templ.go | 1 + testing_/test_obj.go | 4 ++-- testing_/test_obj2.go | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 cmd/gogm/templ.go diff --git a/cmd/gogm/templ.go b/cmd/gogm/templ.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/cmd/gogm/templ.go @@ -0,0 +1 @@ +package main diff --git a/testing_/test_obj.go b/testing_/test_obj.go index 0efd1f7..ca99b44 100644 --- a/testing_/test_obj.go +++ b/testing_/test_obj.go @@ -5,6 +5,6 @@ import "github.com/mindstand/gogm" type ExampleObject struct { gogm.BaseNode - Children []*ExampleObject `gogm:"direction=incoming;relationship=test"` - Parents *ExampleObject `gogm:"direction=outgoing;relationship=test"` + Children []*ExampleObject `gogm:"direction=incoming;relationship=test" json:"children"` + Parents *ExampleObject `gogm:"direction=outgoing;relationship=test" json:"parents"` } diff --git a/testing_/test_obj2.go b/testing_/test_obj2.go index 400a19e..06e76e6 100644 --- a/testing_/test_obj2.go +++ b/testing_/test_obj2.go @@ -5,6 +5,6 @@ import "github.com/mindstand/gogm" type ExampleObject2 struct { gogm.BaseNode - Children2 []*ExampleObject2 `gogm:"direction=incoming;relationship=test"` - Parents2 *ExampleObject2 `gogm:"direction=outgoing;relationship=test"` + Children2 []*ExampleObject2 `gogm:"direction=incoming;relationship=test" json:"children_2"` + Parents2 *ExampleObject2 `gogm:"direction=outgoing;relationship=test" json:"parents_2"` } From 3b60ea19dc34c7b78f1e710aaa856f9ad5b4b338 Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Sun, 1 Dec 2019 17:15:07 -0500 Subject: [PATCH 05/27] gogm gen works --- .gitignore | 2 + cmd/gogm/gen/gen.go | 209 +++++++++++++++++++++++++++++++ cmd/gogm/gen/parse.go | 214 ++++++++++++++++++++++++++++++++ cmd/gogm/gen/templ.go | 277 ++++++++++++++++++++++++++++++++++++++++++ cmd/gogm/gogm.go | 33 +---- cmd/gogm/parse.go | 78 ------------ cmd/gogm/templ.go | 1 - cmd/gogm/util/util.go | 28 +++++ testing_/test_edge.go | 43 +++++++ testing_/test_obj.go | 1 + testing_/test_obj2.go | 1 + testing_/test_omit.go | 5 + 12 files changed, 782 insertions(+), 110 deletions(-) create mode 100644 cmd/gogm/gen/gen.go create mode 100644 cmd/gogm/gen/parse.go create mode 100644 cmd/gogm/gen/templ.go delete mode 100644 cmd/gogm/parse.go delete mode 100644 cmd/gogm/templ.go create mode 100644 cmd/gogm/util/util.go create mode 100644 testing_/test_edge.go create mode 100644 testing_/test_omit.go diff --git a/.gitignore b/.gitignore index 43a9cd5..541fb60 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ *.out .idea/ vendor/ +# omit the generated file +testing_/linking.go diff --git a/cmd/gogm/gen/gen.go b/cmd/gogm/gen/gen.go new file mode 100644 index 0000000..9b5bd32 --- /dev/null +++ b/cmd/gogm/gen/gen.go @@ -0,0 +1,209 @@ +package gen + +import ( + "bytes" + "errors" + "fmt" + dsl "github.com/mindstand/go-cypherdsl" + "github.com/mindstand/gogm/cmd/gogm/util" + "html/template" + "log" + "os" + "path/filepath" + "strings" +) + +func Generate(directory string) error { + confs := map[string][]*relConf{} + imps := map[string][]string{} + var edges []string + packageName := "" + + err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error { + if info.IsDir() { + return nil + } + + if err != nil { + log.Println("failed here") + return err + } + + if strings.Contains(path, ".go") { + err := parseFile(path, &confs, &edges, imps, &packageName) + if err != nil { + return err + } + } + + return nil + }) + if err != nil { + return err + } + + var imports []string + + for _, imp := range imps { + imports = append(imports, imp...) + } + + imports = util.SliceUniqMap(imports) + + for i := 0; i < len(imports); i++ { + imports[i] = strings.Replace(imports[i], "\"", "", -1) + } + + relations := make(map[string][]*relConf) + + // sort out relationships + for _, fields := range confs { + for _, field := range fields { + if field == nil { + return errors.New("field can not be nil") + } + + if _, ok := relations[field.RelationshipName]; ok { + relations[field.RelationshipName] = append(relations[field.RelationshipName], field) + } else { + relations[field.RelationshipName] = []*relConf{field} + } + } + } + + // validate relationships (i.e even number) + for name, rel := range relations { + if len(rel) % 2 != 0 { + return fmt.Errorf("relationship [%s] is invalid", name) + } + } + + funcs := make(map[string][]*tplRelConf) + + // set template stuff + for _, rels := range relations { + for _, rel := range rels { + tplRel := &tplRelConf{ + StructName: rel.NodeName, + StructField: rel.Field, + OtherStructName: rel.Type, + StructFieldIsMany: rel.IsMany, + } + + var isSpecialEdge bool + + if util.StringSliceContains(edges, rel.Type) { + tplRel.UsesSpecialEdge = true + tplRel.SpecialEdgeType = rel.Type + tplRel.SpecialEdgeDirection = rel.Direction == dsl.DirectionIncoming + isSpecialEdge = true + } + + searchLoop: + for _, lookup := range rels { + //check special edge + if rel.Type != lookup.NodeName && !isSpecialEdge{ + continue + } + + switch rel.Direction { + case dsl.DirectionOutgoing: + if lookup.Direction == dsl.DirectionIncoming { + tplRel.OtherStructField = lookup.Field + tplRel.OtherStructFieldIsMany = lookup.IsMany + if isSpecialEdge { + tplRel.OtherStructName = lookup.NodeName + } + break searchLoop + } else { + continue + } + + case dsl.DirectionIncoming: + if lookup.Direction == dsl.DirectionOutgoing { + tplRel.OtherStructField = lookup.Field + tplRel.OtherStructFieldIsMany = lookup.IsMany + if isSpecialEdge { + tplRel.OtherStructName = lookup.NodeName + } + break searchLoop + } else { + continue + } + + case dsl.DirectionNone: + if lookup.Direction == dsl.DirectionNone { + tplRel.OtherStructField = lookup.Field + tplRel.OtherStructFieldIsMany = lookup.IsMany + if isSpecialEdge { + tplRel.OtherStructName = lookup.NodeName + } + break searchLoop + } else { + continue + } + + case dsl.DirectionBoth: + if lookup.Direction == dsl.DirectionBoth { + tplRel.OtherStructField = lookup.Field + tplRel.OtherStructFieldIsMany = lookup.IsMany + if isSpecialEdge { + tplRel.OtherStructName = lookup.NodeName + } + break searchLoop + } else { + continue + } + + default: + return fmt.Errorf("invalid direction [%v]", rel.Direction) + } + } + + if tplRel.OtherStructField == "" { + return fmt.Errorf("oposite side not found for node [%s]", rel.NodeName) + } + + if _, ok := funcs[rel.NodeName]; ok { + funcs[rel.NodeName] = append(funcs[rel.NodeName], tplRel) + } else { + funcs[rel.NodeName] = []*tplRelConf{tplRel} + } + } + } + + //write templates out + tpl := template.New("linkFile") + + //register templates + for _, templateString := range []string{singleLink, linkMany, linkSpec, unlinkSingle, unlinkMulti, unlinkSpec, masterTpl} { + tpl, err = tpl.Parse(templateString) + if err != nil { + return err + } + } + + buf := new(bytes.Buffer) + err = tpl.Execute(buf, templateConfig{ + Imports: imports, + PackageName: packageName, + Funcs: funcs, + }) + if err != nil { + return err + } + + f, err := os.Create(fmt.Sprintf("%s/linking.go", directory)) + if err != nil { + return err + } + + lenBytes, err := f.Write(buf.Bytes()) + if err != nil { + return err + } + + log.Println(lenBytes) + + return f.Close() +} diff --git a/cmd/gogm/gen/parse.go b/cmd/gogm/gen/parse.go new file mode 100644 index 0000000..4e04e15 --- /dev/null +++ b/cmd/gogm/gen/parse.go @@ -0,0 +1,214 @@ +package gen + +import ( + "bytes" + "errors" + go_cypherdsl "github.com/mindstand/go-cypherdsl" + "go/ast" + "go/parser" + "go/printer" + "go/token" + "log" + "strings" +) + +type relConf struct { + NodeName string + Field string + RelationshipName string + Type string + IsMany bool + Direction go_cypherdsl.Direction +} + +func parseFile(filePath string, confs *map[string][]*relConf, edges *[]string, imports map[string][]string, packageName *string) error { + fset := token.NewFileSet() + node, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments) + if err != nil { + log.Fatal(err) + } + + if node.Scope != nil { + *packageName = node.Name.Name + if node.Scope.Objects != nil && len(node.Scope.Objects) != 0 { + for label, config := range node.Scope.Objects { + tSpec, ok := config.Decl.(*ast.TypeSpec) + if !ok { + continue + } + + strType, ok := tSpec.Type.(*ast.StructType) + if !ok { + continue + } + + if node.Imports != nil && len(node.Imports) != 0 { + var imps []string + + for _, impSpec := range node.Imports { + imps = append(imps, impSpec.Path.Value) + } + + imports[label] = imps + } + + //check if its a special edge + isEdge, err := parseGogmEdge(node, label) + if err != nil { + return err + } + + if !isEdge { + (*confs)[label] = []*relConf{} + err = parseGogmNode(strType, confs, label, fset) + if err != nil { + return err + } + } else { + *edges = append(*edges, label) + } + + } + } + } + + return nil +} + +func parseGogmEdge(node *ast.File, label string) (bool, error) { + if node == nil { + return false, errors.New("node can not be nil") + } + + var GetStartNode, GetStartNodeType, SetStartNode, GetEndNode, GetEndNodeType, SetEndNode bool + + for _, decl := range node.Decls { + funcDecl, ok := decl.(*ast.FuncDecl) + if !ok { + continue + } + + if len(funcDecl.Recv.List) != 0 { + if len(funcDecl.Recv.List[0].Names) != 0 { + decl, ok := funcDecl.Recv.List[0].Names[0].Obj.Decl.(*ast.Field) + if !ok { + continue + } + + startType, ok := decl.Type.(*ast.StarExpr) + if !ok { + continue + } + + x, ok := startType.X.(*ast.Ident) + if !ok { + continue + } + + //check that the function is the right type + if x.Name != label { + continue + } + } + } else { + continue + } + + switch funcDecl.Name.Name { + case "GetStartNode": + GetStartNode = true + break + case "GetStartNodeType": + GetStartNodeType = true + break + case "SetStartNode": + SetStartNode = true + break + case "GetEndNode": + GetEndNode = true + break + case "GetEndNodeType": + GetEndNodeType = true + break + case "SetEndNode": + SetEndNode = true + break + default: + continue + } + } + + //check if its an edge node + if !GetStartNode || !GetStartNodeType || !SetStartNode || !GetEndNode || !GetEndNodeType || !SetEndNode { + return false, nil + } + + return true, nil +} + +func parseGogmNode(strType *ast.StructType, confs *map[string][]*relConf, label string, fset *token.FileSet) error { + if strType.Fields != nil && strType.Fields.List != nil && len(strType.Fields.List) != 0 { + fieldLoop: + for _, field := range strType.Fields.List { + if field.Tag != nil && field.Tag.Value != "" { + parts := strings.Split(field.Tag.Value, " ") + for _, part := range parts { + if !strings.Contains(part, "gogm") { + continue + } + part = strings.Replace(strings.Replace(part, "`gogm:", "", -1), "\"", "", -1) + if strings.Contains(part, "relationship") && strings.Contains(part, "direction") { + gogmParts := strings.Split(part, ";") + + var dir go_cypherdsl.Direction + var relName string + for _, p := range gogmParts { + if strings.Contains(p, "direction") { + str := strings.ToLower(strings.Replace(strings.Replace(p, "direction=", "", -1), "\"", "", -1)) + switch str{ + case "incoming": + dir = go_cypherdsl.DirectionIncoming + break + case "outgoing": + dir = go_cypherdsl.DirectionOutgoing + break + case "both": + dir = go_cypherdsl.DirectionBoth + break + case "none": + dir = go_cypherdsl.DirectionNone + break + default: + log.Printf("direction %s not found", str) + continue fieldLoop + } + } else if strings.Contains(part, "relationship") { + relName = strings.ToLower(strings.Replace(strings.Replace(p, "relationship=", "", -1), "\"", "", -1)) + } + } + + var typeNameBuf bytes.Buffer + + err := printer.Fprint(&typeNameBuf, fset, field.Type) + if err != nil { + log.Fatal(err) + } + + t := typeNameBuf.String() + + (*confs)[label] = append((*confs)[label], &relConf{ + Field: field.Names[0].Name, + RelationshipName: relName, + Type: strings.Replace(strings.Replace(t, "[]", "", -1), "*", "", -1), + IsMany: strings.Contains(t, "[]"), + Direction: dir, + NodeName: label, + }) + } + } + } + } + } + + return nil +} \ No newline at end of file diff --git a/cmd/gogm/gen/templ.go b/cmd/gogm/gen/templ.go new file mode 100644 index 0000000..eb88bf7 --- /dev/null +++ b/cmd/gogm/gen/templ.go @@ -0,0 +1,277 @@ +package gen + + +//expect .StructName .OtherStructName .StructField .OtherStructField .StructFieldIsMany .OtherStructFieldIsMany +var linkSpec = ` +{{ define "linkSpec" }} +func(l *{{ .StructName }}) LinkTo{{ .OtherStructName }}{{.OtherStructField}}(target *{{ .OtherStructName }}, edge *{{.SpecialEdgeType}}) error { + if target == nil { + return errors.New("start and end can not be nil") + } + + if edge == nil { + return errors.New("edge can not be nil") + } + {{ if .SpecialEdgeDirection }} + err := edge.SetStartNode(l) + if err != nil { + return err + } + + err = edge.SetEndNode(target) + if err != nil { + return err + }{{ else }} + err := edge.SetStartNode(target) + if err != nil { + return err + } + + err = edge.SetEndNode(l) + if err != nil { + return err + }{{ end }} + {{if .StructFieldIsMany }} + if l.{{ .StructField }} == nil { + l.{{ .StructField }} = make([]*{{ .SpecialEdgeType }}, 0, 1) + l.{{ .StructField }}[0] = edge + } else { + l.{{ .StructField }} = append(l.{{ .StructField }}, edge) + }{{ else }} + l.{{ .StructField }} = edge{{ end }} + {{if .OtherStructFieldIsMany }} + if target.{{ .OtherStructField }} == nil { + target.{{ .OtherStructField }} = make([]*{{ .SpecialEdgeType }}, 0, 1) + target.{{ .OtherStructField }}[0] = edge + } else { + target.{{ .OtherStructField }} = append(target.{{ .OtherStructField }}, edge) + }{{ else }} + target.{{ .OtherStructField }} = edge{{ end }} + + return nil +}{{ end }} +` + +var singleLink = ` +{{ define "linkSingle" }}func(l *{{ .StructName }}) LinkTo{{ .OtherStructName }}{{.OtherStructField}}(target *{{ .OtherStructName }}) error { + if target == nil { + return errors.New("start and end can not be nil") + } + {{if .StructFieldIsMany }} + if l.{{ .StructField }} == nil { + l.{{ .StructField }} = make([]*{{ .OtherStructName }}, 0, 1) + l.{{ .StructField }}[0] = target + } else { + l.{{ .StructField }} = append(l.{{ .StructField }}, target) + }{{ else }} + l.{{ .StructField }} = target{{ end }} + {{if .OtherStructFieldIsMany }} + if target.{{ .OtherStructField }} == nil { + target.{{ .OtherStructField }} = make([]*{{ .StructName }}, 0, 1) + target.{{ .OtherStructField }}[0] = l + } else { + target.{{ .OtherStructField }} = append(target.{{ .OtherStructField }}, l) + }{{ else }} + target.{{ .OtherStructField }} = l{{ end }} + + return nil +}{{ end }} +` + +var linkMany = ` +{{ define "linkMany" }} +func(l *{{ .StructName }}) LinkTo{{ .OtherStructName }}{{.OtherStructField}}(targets ...*{{ .OtherStructName }}) error { + if targets == nil { + return errors.New("start and end can not be nil") + } + + for _, target := range targets { + {{if .StructFieldIsMany }} + if l.{{ .StructField }} == nil { + l.{{ .StructField }} = make([]*{{ .OtherStructName }}, 0, 1) + l.{{ .StructField }}[0] = target + } else { + l.{{ .StructField }} = append(l.{{ .StructField }}, target) + }{{ else }} + l.{{ .StructField }} = target{{ end }} + {{if .OtherStructFieldIsMany }} + if target.{{ .OtherStructField }} == nil { + target.{{ .OtherStructField }} = make([]*{{ .StructName }}, 0, 1) + target.{{ .OtherStructField }}[0] = l + } else { + target.{{ .OtherStructField }} = append(target.{{ .OtherStructField }}, l) + }{{ else }} + target.{{ .OtherStructField }} = l{{ end }} + } + + return nil +}{{ end }} +` + +var unlinkSingle = ` +{{ define "unlinkSingle" }}func(l *{{ .StructName }}) UnlinkFrom{{ .OtherStructName }}{{.OtherStructField}}(target *{{ .OtherStructName }}) error { + if target == nil { + return errors.New("start and end can not be nil") + } + {{if .StructFieldIsMany }} + if l.{{ .StructField }} != nil { + for i, unlinkTarget := range l.{{ .StructField }} { + if unlinkTarget.UUID == target.UUID { + a := l.{{ .StructField }} + a[i] = a[len(a)-1] + a[len(a)-1] = nil + a = a[:len(a)-1] + break + } + } + }{{ else }} + l.{{ .StructField }} = nil{{ end }} + {{if .OtherStructFieldIsMany }} + if target.{{ .OtherStructField }} == nil { + for i, unlinkTarget := range target.{{ .OtherStructField }} { + if unlinkTarget.UUID == target.UUID { + a := target.{{ .OtherStructField }} + a[i] = a[len(a)-1] + a[len(a)-1] = nil + a = a[:len(a)-1] + break + } + } + }{{ else }} + target.{{ .OtherStructField }} = nil{{ end }} + + return nil +}{{ end }} +` + +var unlinkMulti = ` +{{ define "unlinkMulti" }}func(l *{{ .StructName }}) UnlinkFrom{{ .OtherStructName }}{{.OtherStructField}}(targets ...*{{ .OtherStructName }}) error { + if targets == nil { + return errors.New("start and end can not be nil") + } + + for _, target := range targets { + {{if .StructFieldIsMany }} + if l.{{ .StructField }} != nil { + for i, unlinkTarget := range l.{{ .StructField }} { + if unlinkTarget.UUID == target.UUID { + a := l.{{ .StructField }} + a[i] = a[len(a)-1] + a[len(a)-1] = nil + a = a[:len(a)-1] + break + } + } + }{{ else }} + l.{{ .StructField }} = nil{{ end }} + {{if .OtherStructFieldIsMany }} + if target.{{ .OtherStructField }} == nil { + for i, unlinkTarget := range target.{{ .OtherStructField }} { + if unlinkTarget.UUID == target.UUID { + a := target.{{ .OtherStructField }} + a[i] = a[len(a)-1] + a[len(a)-1] = nil + a = a[:len(a)-1] + break + } + } + }{{ else }} + target.{{ .OtherStructField }} = nil{{ end }} + } + + return nil +}{{ end }} +` + +var unlinkSpec = ` +{{ define "unlinkSpec" }}func(l *{{ .StructName }}) UnlinkFrom{{ .OtherStructName }}{{.OtherStructField}}(target *{{ .OtherStructName }}) error { + if target == nil { + return errors.New("start and end can not be nil") + } + {{if .StructFieldIsMany }} + if l.{{ .StructField }} != nil { + for i, unlinkTarget := range l.{{ .StructField }} { + {{ if .SpecialEdgeDirection }} + obj := unlinkTarget.GetStartNode(){{ else }} + obj := unlinkTarget.GetEndNode(){{end}} + + checkObj, ok := obj.(*{{ .OtherStructName }}) + if !ok { + return errors.New("unable to cast unlinkTarget to [{{ .OtherStructName }}]") + } + if checkObj.UUID == target.UUID { + a := l.{{ .StructField }} + a[i] = a[len(a)-1] + a[len(a)-1] = nil + a = a[:len(a)-1] + break + } + } + }{{ else }} + l.{{ .StructField }} = nil{{ end }} + {{if .OtherStructFieldIsMany }} + if target.{{ .OtherStructField }} == nil { + for i, unlinkTarget := range target.{{ .OtherStructField }} { + {{ if .SpecialEdgeDirection }} + obj := unlinkTarget.GetStartNode(){{ else }} + obj := unlinkTarget.GetEndNode(){{end}} + + checkObj, ok := obj.(*{{ .StructName }}) + if !ok { + return errors.New("unable to cast unlinkTarget to [{{ .StructName }}]") + } + if checkObj.UUID == target.UUID { + a := target.{{ .OtherStructField }} + a[i] = a[len(a)-1] + a[len(a)-1] = nil + a = a[:len(a)-1] + break + } + } + }{{ else }} + target.{{ .OtherStructField }} = nil{{ end }} + + return nil +}{{ end }} +` + +var masterTpl = ` +{{ define "linkFile" }}// code generated by gogm; DO NOT EDIT +package {{ .PackageName }} + +import ( + "errors" +) +{{range $key, $val := .Funcs}}{{range $val}} {{ if .UsesSpecialEdge }} +{{ template "linkSpec" . }} + +{{ template "unlinkSpec" . }}{{ else if .StructFieldIsMany}} +{{template "linkMany" .}} + +{{ template "unlinkMulti" .}}{{ else }} +{{ template "linkSingle" .}} + +{{ template "unlinkSingle" . }}{{end}} {{end}} {{end}} {{ end }} +` + +type templateConfig struct { + Imports []string + PackageName string + // type: funcs + Funcs map[string][]*tplRelConf +} + +type tplRelConf struct { + StructName string + StructField string + OtherStructField string + OtherStructName string + StructFieldIsMany bool + OtherStructFieldIsMany bool + + //stuff for special edges + UsesSpecialEdge bool + SpecialEdgeType string + // StructName = Start if true + SpecialEdgeDirection bool +} \ No newline at end of file diff --git a/cmd/gogm/gogm.go b/cmd/gogm/gogm.go index 1ff843c..b7c9603 100644 --- a/cmd/gogm/gogm.go +++ b/cmd/gogm/gogm.go @@ -1,45 +1,16 @@ package main import ( + "github.com/mindstand/gogm/cmd/gogm/gen" "log" - "os" - "path/filepath" - "strings" ) - - func main() { directory := "/home/erictg97/mindstand/repos/gogm/testing_" - - confs := map[string][]*relConf{} - imps := map[string][]string{} - - err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error { - if info.IsDir() { - return nil - } - - if err != nil { - log.Println("failed here") - return err - } - - if strings.Contains(path, ".go") { - err := parseFile(path, &confs, imps) - if err != nil { - log.Fatal(err) - } - } - - return nil - }) + err := gen.Generate(directory) if err != nil { log.Fatal(err) } - - log.Println(confs) - log.Println(imps) } diff --git a/cmd/gogm/parse.go b/cmd/gogm/parse.go deleted file mode 100644 index 6d7b53a..0000000 --- a/cmd/gogm/parse.go +++ /dev/null @@ -1,78 +0,0 @@ -package main - -import ( - "bytes" - "go/ast" - "go/parser" - "go/printer" - "go/token" - "log" - "strings" -) - -type relConf struct { - Field string - Type string - IsMany bool -} - -func parseFile(filePath string, confs *map[string][]*relConf, imports map[string][]string) error { - fset := token.NewFileSet() - node, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments) - if err != nil { - log.Fatal(err) - } - - if node.Scope != nil { - if node.Scope.Objects != nil && len(node.Scope.Objects) != 0 { - for label, config := range node.Scope.Objects { - tSpec, ok := config.Decl.(*ast.TypeSpec) - if !ok { - continue - } - - strType, ok := tSpec.Type.(*ast.StructType) - if !ok { - continue - } - - if node.Imports != nil && len(node.Imports) != 0 { - var imps []string - - for _, impSpec := range node.Imports { - imps = append(imps, impSpec.Path.Value) - } - - imports[label] = imps - } - - (*confs)[label] = []*relConf{} - - if strType.Fields != nil && strType.Fields.List != nil && len(strType.Fields.List) != 0 { - for _, field := range strType.Fields.List { - if field.Tag != nil && field.Tag.Value != "" { - if strings.Contains(field.Tag.Value, "relationship") && strings.Contains(field.Tag.Value, "direction") { - var typeNameBuf bytes.Buffer - - err = printer.Fprint(&typeNameBuf, fset, field.Type) - if err != nil { - log.Fatal(err) - } - - t := typeNameBuf.String() - - (*confs)[label] = append((*confs)[label], &relConf{ - Field: field.Names[0].Name, - Type: t, - IsMany: strings.Contains(t, "[]"), - }) - } - } - } - } - } - } - } - - return nil -} \ No newline at end of file diff --git a/cmd/gogm/templ.go b/cmd/gogm/templ.go deleted file mode 100644 index 06ab7d0..0000000 --- a/cmd/gogm/templ.go +++ /dev/null @@ -1 +0,0 @@ -package main diff --git a/cmd/gogm/util/util.go b/cmd/gogm/util/util.go new file mode 100644 index 0000000..95b63a7 --- /dev/null +++ b/cmd/gogm/util/util.go @@ -0,0 +1,28 @@ +package util + +func SliceUniqMap(s []string) []string { + if s == nil { + return []string{} + } + + seen := make(map[string]struct{}, len(s)) + j := 0 + for _, v := range s { + if _, ok := seen[v]; ok { + continue + } + seen[v] = struct{}{} + s[j] = v + j++ + } + return s[:j] +} + +func StringSliceContains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} diff --git a/testing_/test_edge.go b/testing_/test_edge.go new file mode 100644 index 0000000..bd26dc3 --- /dev/null +++ b/testing_/test_edge.go @@ -0,0 +1,43 @@ +package testing_ + +import ( + "github.com/mindstand/gogm" + "reflect" +) + +type SpecialEdge struct { + gogm.BaseNode + + Start *ExampleObject + End *ExampleObject2 + + SomeField string `gogm:"name=some_field"` +} + +func (s *SpecialEdge) GetStartNode() interface{} { + return s.Start +} + +func (s *SpecialEdge) GetStartNodeType() reflect.Type { + return reflect.TypeOf(&ExampleObject{}) +} + +func (s *SpecialEdge) SetStartNode(v interface{}) error { + s.Start = v.(*ExampleObject) + return nil +} + +func (s *SpecialEdge) GetEndNode() interface{} { + return s.End +} + +func (s *SpecialEdge) GetEndNodeType() reflect.Type { + return reflect.TypeOf(&ExampleObject2{}) +} + +func (s *SpecialEdge) SetEndNode(v interface{}) error { + s.End = v.(*ExampleObject2) + return nil +} + + diff --git a/testing_/test_obj.go b/testing_/test_obj.go index ca99b44..3a2f477 100644 --- a/testing_/test_obj.go +++ b/testing_/test_obj.go @@ -7,4 +7,5 @@ type ExampleObject struct { Children []*ExampleObject `gogm:"direction=incoming;relationship=test" json:"children"` Parents *ExampleObject `gogm:"direction=outgoing;relationship=test" json:"parents"` + Special *SpecialEdge `gogm:"direction=incoming;relationship=special" json:"special"` } diff --git a/testing_/test_obj2.go b/testing_/test_obj2.go index 06e76e6..4d88de5 100644 --- a/testing_/test_obj2.go +++ b/testing_/test_obj2.go @@ -7,4 +7,5 @@ type ExampleObject2 struct { Children2 []*ExampleObject2 `gogm:"direction=incoming;relationship=test" json:"children_2"` Parents2 *ExampleObject2 `gogm:"direction=outgoing;relationship=test" json:"parents_2"` + Special []*SpecialEdge `gogm:"direction=outgoing;relationship=special" json:"special"` } diff --git a/testing_/test_omit.go b/testing_/test_omit.go new file mode 100644 index 0000000..098482d --- /dev/null +++ b/testing_/test_omit.go @@ -0,0 +1,5 @@ +package testing_ + +type OrdinaryNonGogmStruct struct { + SomeField string +} From 6ffbd94da7bf352ea420805c558703496c90f88a Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Sun, 1 Dec 2019 17:40:37 -0500 Subject: [PATCH 06/27] gogm gen works --- .gitignore | 2 - cmd/gogm/gen/templ.go | 84 +++++------ testing_/linking.go | 293 +++++++++++++++++++++++++++++++++++++++ testing_/linking_test.go | 58 ++++++++ 4 files changed, 393 insertions(+), 44 deletions(-) create mode 100644 testing_/linking.go create mode 100644 testing_/linking_test.go diff --git a/.gitignore b/.gitignore index 541fb60..43a9cd5 100644 --- a/.gitignore +++ b/.gitignore @@ -12,5 +12,3 @@ *.out .idea/ vendor/ -# omit the generated file -testing_/linking.go diff --git a/cmd/gogm/gen/templ.go b/cmd/gogm/gen/templ.go index eb88bf7..b54db47 100644 --- a/cmd/gogm/gen/templ.go +++ b/cmd/gogm/gen/templ.go @@ -4,7 +4,7 @@ package gen //expect .StructName .OtherStructName .StructField .OtherStructField .StructFieldIsMany .OtherStructFieldIsMany var linkSpec = ` {{ define "linkSpec" }} -func(l *{{ .StructName }}) LinkTo{{ .OtherStructName }}{{.OtherStructField}}(target *{{ .OtherStructName }}, edge *{{.SpecialEdgeType}}) error { +func(l *{{ .StructName }}) LinkTo{{ .OtherStructName }}OnField{{.StructField}}(target *{{ .OtherStructName }}, edge *{{.SpecialEdgeType}}) error { if target == nil { return errors.New("start and end can not be nil") } @@ -33,7 +33,7 @@ func(l *{{ .StructName }}) LinkTo{{ .OtherStructName }}{{.OtherStructField}}(tar }{{ end }} {{if .StructFieldIsMany }} if l.{{ .StructField }} == nil { - l.{{ .StructField }} = make([]*{{ .SpecialEdgeType }}, 0, 1) + l.{{ .StructField }} = make([]*{{ .SpecialEdgeType }}, 1, 1) l.{{ .StructField }}[0] = edge } else { l.{{ .StructField }} = append(l.{{ .StructField }}, edge) @@ -41,7 +41,7 @@ func(l *{{ .StructName }}) LinkTo{{ .OtherStructName }}{{.OtherStructField}}(tar l.{{ .StructField }} = edge{{ end }} {{if .OtherStructFieldIsMany }} if target.{{ .OtherStructField }} == nil { - target.{{ .OtherStructField }} = make([]*{{ .SpecialEdgeType }}, 0, 1) + target.{{ .OtherStructField }} = make([]*{{ .SpecialEdgeType }}, 1, 1) target.{{ .OtherStructField }}[0] = edge } else { target.{{ .OtherStructField }} = append(target.{{ .OtherStructField }}, edge) @@ -53,13 +53,13 @@ func(l *{{ .StructName }}) LinkTo{{ .OtherStructName }}{{.OtherStructField}}(tar ` var singleLink = ` -{{ define "linkSingle" }}func(l *{{ .StructName }}) LinkTo{{ .OtherStructName }}{{.OtherStructField}}(target *{{ .OtherStructName }}) error { +{{ define "linkSingle" }}func(l *{{ .StructName }}) LinkTo{{ .OtherStructName }}OnField{{.StructField}}(target *{{ .OtherStructName }}) error { if target == nil { return errors.New("start and end can not be nil") } {{if .StructFieldIsMany }} if l.{{ .StructField }} == nil { - l.{{ .StructField }} = make([]*{{ .OtherStructName }}, 0, 1) + l.{{ .StructField }} = make([]*{{ .OtherStructName }}, 1, 1) l.{{ .StructField }}[0] = target } else { l.{{ .StructField }} = append(l.{{ .StructField }}, target) @@ -67,7 +67,7 @@ var singleLink = ` l.{{ .StructField }} = target{{ end }} {{if .OtherStructFieldIsMany }} if target.{{ .OtherStructField }} == nil { - target.{{ .OtherStructField }} = make([]*{{ .StructName }}, 0, 1) + target.{{ .OtherStructField }} = make([]*{{ .StructName }}, 1, 1) target.{{ .OtherStructField }}[0] = l } else { target.{{ .OtherStructField }} = append(target.{{ .OtherStructField }}, l) @@ -80,7 +80,7 @@ var singleLink = ` var linkMany = ` {{ define "linkMany" }} -func(l *{{ .StructName }}) LinkTo{{ .OtherStructName }}{{.OtherStructField}}(targets ...*{{ .OtherStructName }}) error { +func(l *{{ .StructName }}) LinkTo{{ .OtherStructName }}OnField{{.StructField}}(targets ...*{{ .OtherStructName }}) error { if targets == nil { return errors.New("start and end can not be nil") } @@ -88,7 +88,7 @@ func(l *{{ .StructName }}) LinkTo{{ .OtherStructName }}{{.OtherStructField}}(tar for _, target := range targets { {{if .StructFieldIsMany }} if l.{{ .StructField }} == nil { - l.{{ .StructField }} = make([]*{{ .OtherStructName }}, 0, 1) + l.{{ .StructField }} = make([]*{{ .OtherStructName }}, 1, 1) l.{{ .StructField }}[0] = target } else { l.{{ .StructField }} = append(l.{{ .StructField }}, target) @@ -96,7 +96,7 @@ func(l *{{ .StructName }}) LinkTo{{ .OtherStructName }}{{.OtherStructField}}(tar l.{{ .StructField }} = target{{ end }} {{if .OtherStructFieldIsMany }} if target.{{ .OtherStructField }} == nil { - target.{{ .OtherStructField }} = make([]*{{ .StructName }}, 0, 1) + target.{{ .OtherStructField }} = make([]*{{ .StructName }}, 1, 1) target.{{ .OtherStructField }}[0] = l } else { target.{{ .OtherStructField }} = append(target.{{ .OtherStructField }}, l) @@ -109,7 +109,7 @@ func(l *{{ .StructName }}) LinkTo{{ .OtherStructName }}{{.OtherStructField}}(tar ` var unlinkSingle = ` -{{ define "unlinkSingle" }}func(l *{{ .StructName }}) UnlinkFrom{{ .OtherStructName }}{{.OtherStructField}}(target *{{ .OtherStructName }}) error { +{{ define "unlinkSingle" }}func(l *{{ .StructName }}) UnlinkFrom{{ .OtherStructName }}OnField{{.StructField}}(target *{{ .OtherStructName }}) error { if target == nil { return errors.New("start and end can not be nil") } @@ -117,23 +117,23 @@ var unlinkSingle = ` if l.{{ .StructField }} != nil { for i, unlinkTarget := range l.{{ .StructField }} { if unlinkTarget.UUID == target.UUID { - a := l.{{ .StructField }} - a[i] = a[len(a)-1] - a[len(a)-1] = nil - a = a[:len(a)-1] + a := &l.{{ .StructField }} + (*a)[i] = (*a)[len(*a)-1] + (*a)[len(*a)-1] = nil + *a = (*a)[:len(*a)-1] break } } }{{ else }} l.{{ .StructField }} = nil{{ end }} {{if .OtherStructFieldIsMany }} - if target.{{ .OtherStructField }} == nil { + if target.{{ .OtherStructField }} != nil { for i, unlinkTarget := range target.{{ .OtherStructField }} { - if unlinkTarget.UUID == target.UUID { - a := target.{{ .OtherStructField }} - a[i] = a[len(a)-1] - a[len(a)-1] = nil - a = a[:len(a)-1] + if unlinkTarget.UUID == l.UUID { + a := &target.{{ .OtherStructField }} + (*a)[i] = (*a)[len(*a)-1] + (*a)[len(*a)-1] = nil + *a = (*a)[:len(*a)-1] break } } @@ -145,7 +145,7 @@ var unlinkSingle = ` ` var unlinkMulti = ` -{{ define "unlinkMulti" }}func(l *{{ .StructName }}) UnlinkFrom{{ .OtherStructName }}{{.OtherStructField}}(targets ...*{{ .OtherStructName }}) error { +{{ define "unlinkMulti" }}func(l *{{ .StructName }}) UnlinkFrom{{ .OtherStructName }}OnField{{.StructField}}(targets ...*{{ .OtherStructName }}) error { if targets == nil { return errors.New("start and end can not be nil") } @@ -155,23 +155,23 @@ var unlinkMulti = ` if l.{{ .StructField }} != nil { for i, unlinkTarget := range l.{{ .StructField }} { if unlinkTarget.UUID == target.UUID { - a := l.{{ .StructField }} - a[i] = a[len(a)-1] - a[len(a)-1] = nil - a = a[:len(a)-1] + a := &l.{{ .StructField }} + (*a)[i] = (*a)[len(*a)-1] + (*a)[len(*a)-1] = nil + *a = (*a)[:len(*a)-1] break } } }{{ else }} l.{{ .StructField }} = nil{{ end }} {{if .OtherStructFieldIsMany }} - if target.{{ .OtherStructField }} == nil { + if target.{{ .OtherStructField }} != nil { for i, unlinkTarget := range target.{{ .OtherStructField }} { - if unlinkTarget.UUID == target.UUID { - a := target.{{ .OtherStructField }} - a[i] = a[len(a)-1] - a[len(a)-1] = nil - a = a[:len(a)-1] + if unlinkTarget.UUID == l.UUID { + a := &target.{{ .OtherStructField }} + (*a)[i] = (*a)[len(*a)-1] + (*a)[len(*a)-1] = nil + *a = (*a)[:len(*a)-1] break } } @@ -184,7 +184,7 @@ var unlinkMulti = ` ` var unlinkSpec = ` -{{ define "unlinkSpec" }}func(l *{{ .StructName }}) UnlinkFrom{{ .OtherStructName }}{{.OtherStructField}}(target *{{ .OtherStructName }}) error { +{{ define "unlinkSpec" }}func(l *{{ .StructName }}) UnlinkFrom{{ .OtherStructName }}OnField{{.StructField}}(target *{{ .OtherStructName }}) error { if target == nil { return errors.New("start and end can not be nil") } @@ -200,17 +200,17 @@ var unlinkSpec = ` return errors.New("unable to cast unlinkTarget to [{{ .OtherStructName }}]") } if checkObj.UUID == target.UUID { - a := l.{{ .StructField }} - a[i] = a[len(a)-1] - a[len(a)-1] = nil - a = a[:len(a)-1] + a := &l.{{ .StructField }} + (*a)[i] = (*a)[len(*a)-1] + (*a)[len(*a)-1] = nil + *a = (*a)[:len(*a)-1] break } } }{{ else }} l.{{ .StructField }} = nil{{ end }} {{if .OtherStructFieldIsMany }} - if target.{{ .OtherStructField }} == nil { + if target.{{ .OtherStructField }} != nil { for i, unlinkTarget := range target.{{ .OtherStructField }} { {{ if .SpecialEdgeDirection }} obj := unlinkTarget.GetStartNode(){{ else }} @@ -220,11 +220,11 @@ var unlinkSpec = ` if !ok { return errors.New("unable to cast unlinkTarget to [{{ .StructName }}]") } - if checkObj.UUID == target.UUID { - a := target.{{ .OtherStructField }} - a[i] = a[len(a)-1] - a[len(a)-1] = nil - a = a[:len(a)-1] + if checkObj.UUID == l.UUID { + a := &target.{{ .OtherStructField }} + (*a)[i] = (*a)[len(*a)-1] + (*a)[len(*a)-1] = nil + *a = (*a)[:len(*a)-1] break } } diff --git a/testing_/linking.go b/testing_/linking.go new file mode 100644 index 0000000..da3c35f --- /dev/null +++ b/testing_/linking.go @@ -0,0 +1,293 @@ +// code generated by gogm; DO NOT EDIT +package testing_ + +import ( + "errors" +) + + +func(l *ExampleObject) LinkToExampleObjectOnFieldChildren(targets ...*ExampleObject) error { + if targets == nil { + return errors.New("start and end can not be nil") + } + + for _, target := range targets { + + if l.Children == nil { + l.Children = make([]*ExampleObject, 1, 1) + l.Children[0] = target + } else { + l.Children = append(l.Children, target) + } + + target.Parents = l + } + + return nil +} + +func(l *ExampleObject) UnlinkFromExampleObjectOnFieldChildren(targets ...*ExampleObject) error { + if targets == nil { + return errors.New("start and end can not be nil") + } + + for _, target := range targets { + + if l.Children != nil { + for i, unlinkTarget := range l.Children { + if unlinkTarget.UUID == target.UUID { + a := &l.Children + (*a)[i] = (*a)[len(*a)-1] + (*a)[len(*a)-1] = nil + *a = (*a)[:len(*a)-1] + break + } + } + } + + target.Parents = nil + } + + return nil +} +func(l *ExampleObject) LinkToExampleObjectOnFieldParents(target *ExampleObject) error { + if target == nil { + return errors.New("start and end can not be nil") + } + + l.Parents = target + + if target.Children == nil { + target.Children = make([]*ExampleObject, 1, 1) + target.Children[0] = l + } else { + target.Children = append(target.Children, l) + } + + return nil +} + +func(l *ExampleObject) UnlinkFromExampleObjectOnFieldParents(target *ExampleObject) error { + if target == nil { + return errors.New("start and end can not be nil") + } + + l.Parents = nil + + if target.Children != nil { + for i, unlinkTarget := range target.Children { + if unlinkTarget.UUID == l.UUID { + a := &target.Children + (*a)[i] = (*a)[len(*a)-1] + (*a)[len(*a)-1] = nil + *a = (*a)[:len(*a)-1] + break + } + } + } + + return nil +} + +func(l *ExampleObject) LinkToExampleObject2OnFieldSpecial(target *ExampleObject2, edge *SpecialEdge) error { + if target == nil { + return errors.New("start and end can not be nil") + } + + if edge == nil { + return errors.New("edge can not be nil") + } + + err := edge.SetStartNode(l) + if err != nil { + return err + } + + err = edge.SetEndNode(target) + if err != nil { + return err + } + + l.Special = edge + + if target.Special == nil { + target.Special = make([]*SpecialEdge, 1, 1) + target.Special[0] = edge + } else { + target.Special = append(target.Special, edge) + } + + return nil +} + +func(l *ExampleObject) UnlinkFromExampleObject2OnFieldSpecial(target *ExampleObject2) error { + if target == nil { + return errors.New("start and end can not be nil") + } + + l.Special = nil + + if target.Special != nil { + for i, unlinkTarget := range target.Special { + + obj := unlinkTarget.GetStartNode() + + checkObj, ok := obj.(*ExampleObject) + if !ok { + return errors.New("unable to cast unlinkTarget to [ExampleObject]") + } + if checkObj.UUID == l.UUID { + a := &target.Special + (*a)[i] = (*a)[len(*a)-1] + (*a)[len(*a)-1] = nil + *a = (*a)[:len(*a)-1] + break + } + } + } + + return nil +} + +func(l *ExampleObject2) LinkToExampleObject2OnFieldChildren2(targets ...*ExampleObject2) error { + if targets == nil { + return errors.New("start and end can not be nil") + } + + for _, target := range targets { + + if l.Children2 == nil { + l.Children2 = make([]*ExampleObject2, 1, 1) + l.Children2[0] = target + } else { + l.Children2 = append(l.Children2, target) + } + + target.Parents2 = l + } + + return nil +} + +func(l *ExampleObject2) UnlinkFromExampleObject2OnFieldChildren2(targets ...*ExampleObject2) error { + if targets == nil { + return errors.New("start and end can not be nil") + } + + for _, target := range targets { + + if l.Children2 != nil { + for i, unlinkTarget := range l.Children2 { + if unlinkTarget.UUID == target.UUID { + a := &l.Children2 + (*a)[i] = (*a)[len(*a)-1] + (*a)[len(*a)-1] = nil + *a = (*a)[:len(*a)-1] + break + } + } + } + + target.Parents2 = nil + } + + return nil +} +func(l *ExampleObject2) LinkToExampleObject2OnFieldParents2(target *ExampleObject2) error { + if target == nil { + return errors.New("start and end can not be nil") + } + + l.Parents2 = target + + if target.Children2 == nil { + target.Children2 = make([]*ExampleObject2, 1, 1) + target.Children2[0] = l + } else { + target.Children2 = append(target.Children2, l) + } + + return nil +} + +func(l *ExampleObject2) UnlinkFromExampleObject2OnFieldParents2(target *ExampleObject2) error { + if target == nil { + return errors.New("start and end can not be nil") + } + + l.Parents2 = nil + + if target.Children2 != nil { + for i, unlinkTarget := range target.Children2 { + if unlinkTarget.UUID == l.UUID { + a := &target.Children2 + (*a)[i] = (*a)[len(*a)-1] + (*a)[len(*a)-1] = nil + *a = (*a)[:len(*a)-1] + break + } + } + } + + return nil +} + +func(l *ExampleObject2) LinkToExampleObjectOnFieldSpecial(target *ExampleObject, edge *SpecialEdge) error { + if target == nil { + return errors.New("start and end can not be nil") + } + + if edge == nil { + return errors.New("edge can not be nil") + } + + err := edge.SetStartNode(target) + if err != nil { + return err + } + + err = edge.SetEndNode(l) + if err != nil { + return err + } + + if l.Special == nil { + l.Special = make([]*SpecialEdge, 1, 1) + l.Special[0] = edge + } else { + l.Special = append(l.Special, edge) + } + + target.Special = edge + + return nil +} + +func(l *ExampleObject2) UnlinkFromExampleObjectOnFieldSpecial(target *ExampleObject) error { + if target == nil { + return errors.New("start and end can not be nil") + } + + if l.Special != nil { + for i, unlinkTarget := range l.Special { + + obj := unlinkTarget.GetEndNode() + + checkObj, ok := obj.(*ExampleObject) + if !ok { + return errors.New("unable to cast unlinkTarget to [ExampleObject]") + } + if checkObj.UUID == target.UUID { + a := &l.Special + (*a)[i] = (*a)[len(*a)-1] + (*a)[len(*a)-1] = nil + *a = (*a)[:len(*a)-1] + break + } + } + } + + target.Special = nil + + return nil +} \ No newline at end of file diff --git a/testing_/linking_test.go b/testing_/linking_test.go new file mode 100644 index 0000000..2aeacc9 --- /dev/null +++ b/testing_/linking_test.go @@ -0,0 +1,58 @@ +package testing_ + +import ( + "github.com/mindstand/gogm" + "github.com/stretchr/testify/require" + "testing" +) + +func TestLinking(t *testing.T) { + req := require.New(t) + + id1 := "SDFdasasdf" + id2 := "aasdfasdfa" + + obj1 := &ExampleObject{ + BaseNode: gogm.BaseNode{ + Id: 0, + UUID: id1, + LoadMap: map[string]*gogm.RelationLoad{}, + }, + } + + obj2 := &ExampleObject{ + BaseNode: gogm.BaseNode{ + Id: 1, + UUID: id2, + LoadMap: map[string]*gogm.RelationLoad{}, + }, + } + + req.Nil(obj1.LinkToExampleObjectOnFieldParents(obj2)) + + req.Equal(1, len(obj2.Children)) + req.NotNil(obj1.Parents) + + req.Nil(obj1.UnlinkFromExampleObjectOnFieldParents(obj2)) + req.Equal(0, len(obj2.Children)) + req.Nil(obj1.Parents) + + // test special edge + specEdge := &SpecialEdge{ + SomeField: "asdfad", + } + + obj3 := &ExampleObject2{ + BaseNode: gogm.BaseNode{ + UUID: "adfadsfasd", + }, + } + + req.Nil(obj3.LinkToExampleObjectOnFieldSpecial(obj1, specEdge)) + req.Equal(obj1.Special.End.UUID, obj3.UUID) + req.Equal(1, len(obj3.Special)) + + req.Nil(obj1.UnlinkFromExampleObject2OnFieldSpecial(obj3)) + req.Nil(obj1.Special) + req.Equal(0, len(obj3.Special)) +} From b0cbe59eccdd2a39591cc46294b5f25ee0db1ddd Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Sun, 1 Dec 2019 18:15:41 -0500 Subject: [PATCH 07/27] gogm cli for generate works --- cmd/gogm/gogm.go | 16 -------- cmd/gogm/gogm_test.go | 2 - cmd/{gogm => gogmcli}/gen/gen.go | 9 +++-- cmd/{gogm => gogmcli}/gen/parse.go | 0 cmd/{gogm => gogmcli}/gen/templ.go | 0 cmd/gogmcli/gogm.go | 59 ++++++++++++++++++++++++++++++ cmd/{gogm => gogmcli}/util/util.go | 0 go.mod | 3 ++ go.sum | 14 +++++++ 9 files changed, 82 insertions(+), 21 deletions(-) delete mode 100644 cmd/gogm/gogm.go delete mode 100644 cmd/gogm/gogm_test.go rename cmd/{gogm => gogmcli}/gen/gen.go (96%) rename cmd/{gogm => gogmcli}/gen/parse.go (100%) rename cmd/{gogm => gogmcli}/gen/templ.go (100%) create mode 100644 cmd/gogmcli/gogm.go rename cmd/{gogm => gogmcli}/util/util.go (100%) diff --git a/cmd/gogm/gogm.go b/cmd/gogm/gogm.go deleted file mode 100644 index b7c9603..0000000 --- a/cmd/gogm/gogm.go +++ /dev/null @@ -1,16 +0,0 @@ -package main - -import ( - "github.com/mindstand/gogm/cmd/gogm/gen" - "log" -) - -func main() { - directory := "/home/erictg97/mindstand/repos/gogm/testing_" - - err := gen.Generate(directory) - if err != nil { - log.Fatal(err) - } -} - diff --git a/cmd/gogm/gogm_test.go b/cmd/gogm/gogm_test.go deleted file mode 100644 index c9ecbf5..0000000 --- a/cmd/gogm/gogm_test.go +++ /dev/null @@ -1,2 +0,0 @@ -package main - diff --git a/cmd/gogm/gen/gen.go b/cmd/gogmcli/gen/gen.go similarity index 96% rename from cmd/gogm/gen/gen.go rename to cmd/gogmcli/gen/gen.go index 9b5bd32..e4c9f89 100644 --- a/cmd/gogm/gen/gen.go +++ b/cmd/gogmcli/gen/gen.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" dsl "github.com/mindstand/go-cypherdsl" - "github.com/mindstand/gogm/cmd/gogm/util" + "github.com/mindstand/gogm/cmd/gogmcli/util" "html/template" "log" "os" @@ -20,12 +20,15 @@ func Generate(directory string) error { packageName := "" err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error { + if info == nil { + return errors.New("file info is nil") + } + if info.IsDir() { return nil } if err != nil { - log.Println("failed here") return err } @@ -203,7 +206,7 @@ func Generate(directory string) error { return err } - log.Println(lenBytes) + log.Printf("done after writing [%v] bytes!", lenBytes) return f.Close() } diff --git a/cmd/gogm/gen/parse.go b/cmd/gogmcli/gen/parse.go similarity index 100% rename from cmd/gogm/gen/parse.go rename to cmd/gogmcli/gen/parse.go diff --git a/cmd/gogm/gen/templ.go b/cmd/gogmcli/gen/templ.go similarity index 100% rename from cmd/gogm/gen/templ.go rename to cmd/gogmcli/gen/templ.go diff --git a/cmd/gogmcli/gogm.go b/cmd/gogmcli/gogm.go new file mode 100644 index 0000000..4b69d03 --- /dev/null +++ b/cmd/gogmcli/gogm.go @@ -0,0 +1,59 @@ +package main + +import ( + "errors" + "github.com/mindstand/gogm/cmd/gogmcli/gen" + "github.com/urfave/cli/v2" + "log" + "os" +) + +func main() { + app := &cli.App{ + Name: "gogmcli", + HelpName: "gogmcli", + Version: "0.2.0", + Usage: "used for neo4j operations from gogm schema", + Description: "cli for generating and executing migrations with gogm", + EnableBashCompletion: true, + Commands: []*cli.Command{ + { + Name: "generate", + Aliases: []string{ + "g", + "gen", + }, + ArgsUsage: "directory to search and write to", + Usage: "to generate link and unlink functions for nodes", + Action: func(c *cli.Context) error { + directory := c.Args().Get(0) + + if directory == "" { + return errors.New("must specify directory") + } + + log.Printf("generating link and unlink from directory [%s]", directory) + + return gen.Generate(directory) + }, + }, + }, + Authors: []*cli.Author{ + { + Name: "Eric Solender", + Email: "eric@mindstand.com", + }, + { + Name: "Nikita Wootten", + Email: "nikita@mindstand.com", + }, + }, + Copyright: "© MindStand Technologies 2019", + UseShortOptionHandling: true, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} diff --git a/cmd/gogm/util/util.go b/cmd/gogmcli/util/util.go similarity index 100% rename from cmd/gogm/util/util.go rename to cmd/gogmcli/util/util.go diff --git a/go.mod b/go.mod index 0874990..de55f15 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.13 require ( github.com/cornelk/hashmap v1.0.0 + github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/dchest/siphash v1.2.1 // indirect github.com/google/uuid v1.1.1 github.com/kr/pretty v0.1.0 // indirect @@ -13,6 +14,8 @@ require ( github.com/sirupsen/logrus v1.4.2 github.com/stretchr/objx v0.2.0 // indirect github.com/stretchr/testify v1.4.0 + github.com/urfave/cli v1.22.2 + github.com/urfave/cli/v2 v2.0.0 golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect ) diff --git a/go.sum b/go.sum index f1ec15b..a7ea972 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,11 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/cornelk/hashmap v1.0.0 h1:jNHWycAM10SO5Ig76HppMQ69jnbqaziRpqVTNvAxdJQ= github.com/cornelk/hashmap v1.0.0/go.mod h1:8wbysTUDnwJGrPZ1Iwsou3m+An6sldFrJItjRhfegCw= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -42,6 +48,10 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -54,6 +64,10 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= +github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.0.0 h1:+HU9SCbu8GnEUFtIBfuUNXN39ofWViIEJIp6SURMpCg= +github.com/urfave/cli/v2 v2.0.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From 9ea98b371bdc12400daab841431ddf51a9585e12 Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Tue, 3 Dec 2019 11:58:47 -0500 Subject: [PATCH 08/27] so i can revert if needed --- cmd/gogmcli/gen/gen.go | 50 +++++- cmd/gogmcli/gen/parse.go | 93 +++++----- cmd/gogmcli/gogm.go | 23 ++- decoder.go | 18 +- defaults.go | 20 ++- save.go | 373 ++++++++++++++++++++++++++++++++++----- save_test.go | 276 ++++++++++++++++++++++++++--- testing_/linking.go | 158 ++++++++--------- testing_/linking_test.go | 4 +- util.go | 9 + 10 files changed, 801 insertions(+), 223 deletions(-) diff --git a/cmd/gogmcli/gen/gen.go b/cmd/gogmcli/gen/gen.go index e4c9f89..7c01675 100644 --- a/cmd/gogmcli/gen/gen.go +++ b/cmd/gogmcli/gen/gen.go @@ -13,30 +13,44 @@ import ( "strings" ) -func Generate(directory string) error { +func Generate(directory string, debug bool) error { confs := map[string][]*relConf{} imps := map[string][]string{} var edges []string packageName := "" err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error { - if info == nil { - return errors.New("file info is nil") + if err != nil { + return err } - if info.IsDir() { - return nil + if info == nil { + return errors.New("file info is nil") } - if err != nil { - return err + if info.IsDir() && path != directory{ + if debug { + log.Printf("skipping [%s] as it is a directory\n", path) + } + return filepath.SkipDir } if strings.Contains(path, ".go") { + if debug { + log.Printf("parsing go file [%s]\n", path) + } err := parseFile(path, &confs, &edges, imps, &packageName) if err != nil { + if debug { + log.Printf("failed to parse go file [%s] with error '%s'\n", path, err.Error()) + } return err } + if debug { + log.Printf("successfully parsed go file [%s]\n", path) + } + } else if debug { + log.Printf("skipping non go file [%s]\n", path) } return nil @@ -186,6 +200,15 @@ func Generate(directory string) error { } } + if debug { + log.Printf("packageName: [%s]\n", packageName) + } + + if len(funcs) == 0 { + log.Printf("no functions to write, exiting") + return nil + } + buf := new(bytes.Buffer) err = tpl.Execute(buf, templateConfig{ Imports: imports, @@ -206,7 +229,16 @@ func Generate(directory string) error { return err } - log.Printf("done after writing [%v] bytes!", lenBytes) + if debug { + log.Printf("done after writing [%v] bytes!", lenBytes) + } + + err = f.Close() + if err != nil { + return err + } + + log.Printf("wrote link functions to file [%s/linking.go]", directory) - return f.Close() + return nil } diff --git a/cmd/gogmcli/gen/parse.go b/cmd/gogmcli/gen/parse.go index 4e04e15..0a7fa66 100644 --- a/cmd/gogmcli/gen/parse.go +++ b/cmd/gogmcli/gen/parse.go @@ -88,56 +88,61 @@ func parseGogmEdge(node *ast.File, label string) (bool, error) { continue } - if len(funcDecl.Recv.List) != 0 { - if len(funcDecl.Recv.List[0].Names) != 0 { - decl, ok := funcDecl.Recv.List[0].Names[0].Obj.Decl.(*ast.Field) - if !ok { - continue - } + if funcDecl != nil { + if funcDecl.Recv != nil { + if funcDecl.Recv.List != nil { + if len(funcDecl.Recv.List) != 0 { + if len(funcDecl.Recv.List[0].Names) != 0 { + decl, ok := funcDecl.Recv.List[0].Names[0].Obj.Decl.(*ast.Field) + if !ok { + continue + } - startType, ok := decl.Type.(*ast.StarExpr) - if !ok { - continue - } + startType, ok := decl.Type.(*ast.StarExpr) + if !ok { + continue + } - x, ok := startType.X.(*ast.Ident) - if !ok { - continue - } + x, ok := startType.X.(*ast.Ident) + if !ok { + continue + } - //check that the function is the right type - if x.Name != label { - continue + //check that the function is the right type + if x.Name != label { + continue + } + } + } else { + continue + } + + switch funcDecl.Name.Name { + case "GetStartNode": + GetStartNode = true + break + case "GetStartNodeType": + GetStartNodeType = true + break + case "SetStartNode": + SetStartNode = true + break + case "GetEndNode": + GetEndNode = true + break + case "GetEndNodeType": + GetEndNodeType = true + break + case "SetEndNode": + SetEndNode = true + break + default: + continue + } } } - } else { - continue - } - - switch funcDecl.Name.Name { - case "GetStartNode": - GetStartNode = true - break - case "GetStartNodeType": - GetStartNodeType = true - break - case "SetStartNode": - SetStartNode = true - break - case "GetEndNode": - GetEndNode = true - break - case "GetEndNodeType": - GetEndNodeType = true - break - case "SetEndNode": - SetEndNode = true - break - default: - continue } } - //check if its an edge node if !GetStartNode || !GetStartNodeType || !SetStartNode || !GetEndNode || !GetEndNodeType || !SetEndNode { return false, nil @@ -164,7 +169,7 @@ func parseGogmNode(strType *ast.StructType, confs *map[string][]*relConf, label var relName string for _, p := range gogmParts { if strings.Contains(p, "direction") { - str := strings.ToLower(strings.Replace(strings.Replace(p, "direction=", "", -1), "\"", "", -1)) + str := strings.ToLower(strings.Replace(strings.Replace(strings.Replace(p, "direction=", "", -1), "\"", "", -1), "`", "", -1)) switch str{ case "incoming": dir = go_cypherdsl.DirectionIncoming diff --git a/cmd/gogmcli/gogm.go b/cmd/gogmcli/gogm.go index 4b69d03..7955fc5 100644 --- a/cmd/gogmcli/gogm.go +++ b/cmd/gogmcli/gogm.go @@ -9,11 +9,13 @@ import ( ) func main() { + var debug bool + app := &cli.App{ Name: "gogmcli", HelpName: "gogmcli", Version: "0.2.0", - Usage: "used for neo4j operations from gogm schema", + Usage: "used for neo4j operations from gogm schema", Description: "cli for generating and executing migrations with gogm", EnableBashCompletion: true, Commands: []*cli.Command{ @@ -24,7 +26,7 @@ func main() { "gen", }, ArgsUsage: "directory to search and write to", - Usage: "to generate link and unlink functions for nodes", + Usage: "to generate link and unlink functions for nodes", Action: func(c *cli.Context) error { directory := c.Args().Get(0) @@ -32,9 +34,11 @@ func main() { return errors.New("must specify directory") } - log.Printf("generating link and unlink from directory [%s]", directory) + if debug { + log.Printf("generating link and unlink from directory [%s]", directory) + } - return gen.Generate(directory) + return gen.Generate(directory, debug) }, }, }, @@ -48,8 +52,17 @@ func main() { Email: "nikita@mindstand.com", }, }, - Copyright: "© MindStand Technologies 2019", + Copyright: "© MindStand Technologies, Inc 2019", UseShortOptionHandling: true, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "debug", + Aliases: []string{"d"}, + Usage: "execute in debug mode", + Value: false, + Destination: &debug, + }, + }, } err := app.Run(os.Args) diff --git a/decoder.go b/decoder.go index a4b3f84..641f997 100644 --- a/decoder.go +++ b/decoder.go @@ -81,7 +81,7 @@ func decode(rawArr [][]interface{}, respObj interface{}) (err error) { } } nodeLookup := make(map[int64]*reflect.Value) - relMaps := make(map[int64]map[string]*RelationLoad) + relMaps := make(map[int64]map[string]*RelationConfig) var pks []int64 rels := make(map[int64]*neoEdgeConfig) labelLookup := map[int64]string{} @@ -147,8 +147,8 @@ func decode(rawArr [][]interface{}, respObj interface{}) (err error) { rt = Single } - newConf := &RelationLoad{ - Ids: []int64{relationConfig.EndNodeId}, + newConf := &RelationConfig{ + Ids: []int64{relationConfig.EndNodeId}, RelationType: rt, } @@ -169,8 +169,8 @@ func decode(rawArr [][]interface{}, respObj interface{}) (err error) { rt = Single } - newConf := &RelationLoad{ - Ids: []int64{relationConfig.StartNodeId}, + newConf := &RelationConfig{ + Ids: []int64{relationConfig.StartNodeId}, RelationType: rt, } @@ -335,7 +335,7 @@ func getPrimaryLabel(rt reflect.Type) string { return rt.Name() } -func sortIsolatedNodes(isolatedNodes []*graph.Node, labelLookup *map[int64]string, nodeLookup *map[int64]*reflect.Value, pks *[]int64, pkLabel string, relMaps *map[int64]map[string]*RelationLoad) error { +func sortIsolatedNodes(isolatedNodes []*graph.Node, labelLookup *map[int64]string, nodeLookup *map[int64]*reflect.Value, pks *[]int64, pkLabel string, relMaps *map[int64]map[string]*RelationConfig) error { if isolatedNodes == nil { return fmt.Errorf("isolatedNodes can not be nil, %w", ErrInternal) } @@ -354,7 +354,7 @@ func sortIsolatedNodes(isolatedNodes []*graph.Node, labelLookup *map[int64]strin } (*nodeLookup)[node.NodeIdentity] = val - (*relMaps)[node.NodeIdentity] = map[string]*RelationLoad{} + (*relMaps)[node.NodeIdentity] = map[string]*RelationConfig{} //primary to return if node.Labels != nil && len(node.Labels) != 0 && node.Labels[0] == pkLabel { @@ -407,7 +407,7 @@ func sortStrictRels(strictRels []*graph.Relationship, labelLookup *map[int64]str return nil } -func sortPaths(paths []*graph.Path, nodeLookup *map[int64]*reflect.Value, rels *map[int64]*neoEdgeConfig, pks *[]int64, pkLabel string, relMaps *map[int64]map[string]*RelationLoad) error { +func sortPaths(paths []*graph.Path, nodeLookup *map[int64]*reflect.Value, rels *map[int64]*neoEdgeConfig, pks *[]int64, pkLabel string, relMaps *map[int64]map[string]*RelationConfig) error { if paths == nil { return fmt.Errorf("paths is empty, that shouldn't have happened, %w", ErrInternal) } @@ -431,7 +431,7 @@ func sortPaths(paths []*graph.Path, nodeLookup *map[int64]*reflect.Value, rels * } (*nodeLookup)[node.NodeIdentity] = val - (*relMaps)[node.NodeIdentity] = map[string]*RelationLoad{} + (*relMaps)[node.NodeIdentity] = map[string]*RelationConfig{} //primary to return if node.Labels != nil && len(node.Labels) != 0 && node.Labels[0] == pkLabel { diff --git a/defaults.go b/defaults.go index 15f1a35..a1560b0 100644 --- a/defaults.go +++ b/defaults.go @@ -3,18 +3,22 @@ package gogm const loadMapField = "LoadMap" type BaseNode struct { - Id int64 `json:"-" gogm:"name=id"` - UUID string `json:"uuid" gogm:"pk;name=uuid"` - LoadMap map[string]*RelationLoad `json:"-" gogm:"-"` + Id int64 `json:"-" gogm:"name=id"` + UUID string `json:"uuid" gogm:"pk;name=uuid"` + // field -- relations + LoadMap map[string]*RelationConfig `json:"-" gogm:"-"` } type RelationType int + const ( Single RelationType = 0 - Multi RelationType = 1 + Multi RelationType = 1 ) -type RelationLoad struct { - Ids []int64 `json:"-"` - RelationType RelationType `json:"-"` -} \ No newline at end of file +type RelationConfig struct { + Ids []int64 `json:"-" gomg:"-"` + //used to replace for new nodes + UUIDs []string `json:"-"` + RelationType RelationType `json:"-" gomg:"-"` +} diff --git a/save.go b/save.go index b613194..916e774 100644 --- a/save.go +++ b/save.go @@ -6,6 +6,7 @@ import ( dsl "github.com/mindstand/go-cypherdsl" driver "github.com/mindstand/golang-neo4j-bolt-driver" "reflect" + "sync" ) const maxSaveDepth = 10 @@ -25,8 +26,8 @@ type relCreateConf struct { } type relDelConf struct { - StartNodeUUID string - EndNodeUUID string + StartNodeId string + EndNodeId int64 } func saveDepth(sess *driver.BoltConn, obj interface{}, depth int) error { @@ -43,7 +44,7 @@ func saveDepth(sess *driver.BoltConn, obj interface{}, depth int) error { } if depth > maxSaveDepth { - return fmt.Errorf("saving depth of (%v) is currently not supported, maximum depth is (%v)", depth, maxSaveDepth) + return fmt.Errorf("saving depth of (%v) is currently not supported, maximum depth is (%v), %w", depth, maxSaveDepth, ErrConfiguration) } //validate that obj is a pointer @@ -66,11 +67,20 @@ func saveDepth(sess *driver.BoltConn, obj interface{}, depth int) error { //signature is [LABEL] []{config} relations := map[string][]relCreateConf{} - dels := []*relDelConf{} + // node id -- [field] config + oldRels := map[string]map[string]*RelationConfig{} + curRels := map[string]map[string]*RelationConfig{} + newIdRef := int64(-1) + newIdMap := map[int64]string{} + + // uuid -> reflect value + nodeRef := map[string]*reflect.Value{} + + newNodes := []*string{} rootVal := reflect.ValueOf(obj) - err := parseStruct("", "", false, 0, nil, &rootVal, 0, depth, &nodes, &relations, dels) + err := parseStruct("", "", false, 0, nil, &rootVal, 0, depth, &nodes, &relations, &curRels, &oldRels, &newNodes, &nodeRef, newIdRef, &newIdMap) if err != nil { return err } @@ -80,22 +90,161 @@ func saveDepth(sess *driver.BoltConn, obj interface{}, depth int) error { return err } - //no relations to make - if len(ids) == 1 { + //calculate dels + dels := calculateDels(oldRels, curRels) + + var wg sync.WaitGroup + var err1, err2, err3 error + //fix the cur rels and write them to their perspective nodes + wg.Add(1) + go func(wg *sync.WaitGroup, _curRels *map[string]map[string]*RelationConfig, _nodeRef *map[string]*reflect.Value, _ids *map[string]int64, _newIdMap *map[int64]string, _err *error) { + if len(newNodes) != 0 { + for _, relConfs := range *_curRels { + for _, relConf := range relConfs { + //if len(relConf.UUIDs) != 0 { + // for _, uuid := range relConf.UUIDs { + // if id, ok := (*_ids)[uuid]; ok { + // relConf.Ids = append(relConf.Ids, id) + // } else { + // _err = fmt.Errorf("id not found for node [%s]", uuid) + // return + // } + // } + //} + + //todo make this more efficient + for i, id := range relConf.Ids { + if id < 0 { + if uuid, ok := (*_newIdMap)[id]; ok { + if actualId, ok := (*_ids)[uuid]; ok { + relConf.Ids[i] = actualId + } else { + *_err = fmt.Errorf("actual id not found for uuid [%s]", uuid) + wg.Done() + return + } + } else { + *_err = fmt.Errorf("uuid not found for holder id [%v]", id) + wg.Done() + return + } + } + } + + //reset uuids + relConf.UUIDs = []string{} + } + } + } + + for uuid, val := range *_nodeRef { + loadConf, ok := (*_curRels)[uuid] + if !ok { + *_err = fmt.Errorf("load config not found for node [%s]", uuid) + wg.Done() + return + } + + //handle if its a pointer + if val.Kind() == reflect.Ptr { + *val = val.Elem() + } + + id, ok := (*_ids)[uuid] + if !ok { + *_err = fmt.Errorf("GraphId not found for node [%s]", uuid) + wg.Done() + return + } + + reflect.Indirect(*val).FieldByName("LoadMap").Set(reflect.ValueOf(loadConf)) + reflect.Indirect(*val).FieldByName("Id").Set(reflect.ValueOf(id)) + } + wg.Done() + }(&wg, &curRels, &nodeRef, &ids, &newIdMap, &err3) + + //execute concurrently + + if len(dels) != 0 { + wg.Add(1) + + go func(wg *sync.WaitGroup, _dels map[string][]int64, _conn *driver.BoltConn, _err *error) { + err := removeRelations(_conn, _dels) + if err != nil { + *_err = err + } + wg.Done() + }(&wg, dels, sess, &err1) + } + + if len(relations) != 0 { + wg.Add(1) + go func(wg *sync.WaitGroup, _conn *driver.BoltConn, _relations map[string][]relCreateConf, _ids map[string]int64, _err *error) { + err := relateNodes(_conn, _relations, _ids) + if err != nil { + *_err = err + } + wg.Done() + }(&wg, sess, relations, ids, &err2) + } + + wg.Wait() + + if err1 != nil || err2 != nil || err3 != nil{ + return fmt.Errorf("delErr=(%v) | relErr=(%v) | reallocErr=(%v)", err1, err2, err3) + } else { return nil } +} - if len(dels) != 0 { - err = removeRelations(sess, dels) - if err != nil { - return err +func calculateDels(oldRels, curRels map[string]map[string]*RelationConfig) map[string][]int64 { + if len(oldRels) == 0 { + return map[string][]int64{} + } + + dels := map[string][]int64{} + + for uuid, oldRelConf := range oldRels { + curRelConf, ok := curRels[uuid] + deleteAllRels := false + if !ok { + //this means that the node is gone, remove all rels to this node + deleteAllRels = true + } else { + for field, oldConf := range oldRelConf { + curConf, ok := curRelConf[field] + deleteAllRelsOnField := false + if !ok { + //this means that either the field has been removed or there are no more rels on this field, + //either way delete anything left over + deleteAllRelsOnField = true + } + for _, id := range oldConf.Ids { + //check if this id is new rels in the same location + if deleteAllRels || deleteAllRelsOnField{ + if _, ok := dels[uuid]; !ok { + dels[uuid] = []int64{id} + } else { + dels[uuid] = append(dels[uuid], id) + } + } else { + if !int64SliceContains(curConf.Ids, id) { + if _, ok := dels[uuid]; !ok { + dels[uuid] = []int64{id} + } else { + dels[uuid] = append(dels[uuid], id) + } + } + } + } + } } } - return relateNodes(sess, relations, ids) + return dels } -func removeRelations(conn *driver.BoltConn, dels []*relDelConf) error { +func removeRelations(conn *driver.BoltConn, dels map[string][]int64) error { if dels == nil || len(dels) == 0 { return nil } @@ -106,10 +255,10 @@ func removeRelations(conn *driver.BoltConn, dels []*relDelConf) error { var params []interface{} - for _, delConf := range dels { + for uuid, ids := range dels { params = append(params, map[string]interface{}{ - "startNodeId": delConf.StartNodeUUID, - "endNodeId": delConf.EndNodeUUID, + "startNodeId": uuid, + "endNodeIds": ids, }) } @@ -120,13 +269,6 @@ func removeRelations(conn *driver.BoltConn, dels []*relDelConf) error { return fmt.Errorf("%s, %w", err.Error(), ErrInternal) } - endParams, err := dsl.ParamsFromMap(map[string]interface{}{ - "uuid": dsl.ParamString("{row.endNodeId}"), - }) - if err != nil { - return fmt.Errorf("%s, %w", err.Error(), ErrInternal) - } - res, err := dsl.QB(). Cypher("UNWIND {rows} as row"). Match(dsl.Path(). @@ -137,8 +279,8 @@ func removeRelations(conn *driver.BoltConn, dels []*relDelConf) error { Name: "e", }).V(dsl.V{ Name: "end", - Params: endParams, }).Build()). + Cypher("id(end) IN row.endNodeIds"). Delete(false, "e"). WithNeo(conn). Exec(map[string]interface{}{ @@ -351,7 +493,9 @@ func parseValidate(currentDepth, maxDepth int, current *reflect.Value, nodesPtr return nil } -func parseStruct(parentId, edgeLabel string, parentIsStart bool, direction dsl.Direction, edgeParams map[string]interface{}, current *reflect.Value, currentDepth int, maxDepth int, nodesPtr *map[string]map[string]nodeCreateConf, relationsPtr *map[string][]relCreateConf, dels []*relDelConf) error { +func parseStruct(parentId, edgeLabel string, parentIsStart bool, direction dsl.Direction, edgeParams map[string]interface{}, current *reflect.Value, + currentDepth int, maxDepth int, nodesPtr *map[string]map[string]nodeCreateConf, relationsPtr *map[string][]relCreateConf, curRels , oldRels *map[string]map[string]*RelationConfig, + newNodes *[]*string, nodeRef *map[string]*reflect.Value, newIdRef int64, newIdMap *map[int64]string) error { //check if its done if currentDepth > maxDepth { return nil @@ -389,6 +533,57 @@ func parseStruct(parentId, edgeLabel string, parentIsStart bool, direction dsl.D return err } + // set old rels to current load map of the node + (*curRels)[id] = map[string]*RelationConfig{} + + if !isNewNode { + if _, ok := (*oldRels)[id]; !ok{ + iConf := reflect.Indirect(*current).FieldByName("LoadMap").Interface() + + var relConf map[string]*RelationConfig + + if iConf != nil { + relConf, ok = iConf.(map[string]*RelationConfig) + if !ok { + relConf = map[string]*RelationConfig{} + } + } + + (*oldRels)[id] = relConf + } + } else { + //if parentField != "" { + // if _, ok := (*curRels)[parentId]; !ok { + // (*curRels)[parentId] = map[string]*RelationConfig{} + // } + // + // if (*curRels)[parentId][parentField] == nil { + // (*curRels)[parentId][parentField] = &RelationConfig{ + // Ids: []int64{}, + // UUIDs: []string{}, + // RelationType: 0, + // } + // } + // (*curRels)[parentId][parentField].UUIDs = append((*curRels)[parentId][parentField].UUIDs, id) + //} + //this was already set i guess + for { + if _, ok :=(*newIdMap)[newIdRef]; ok { + newIdRef-- + } else { + break + } + } + + (*newIdMap)[newIdRef] = id + *newNodes = append(*newNodes, &id) + } + + //set the reflect pointer so we can update the map later + if _, ok := (*nodeRef)[id]; !ok { + (*nodeRef)[id] = current + } + //convert params params, err := toCypherParamsMap(*current, currentConf) if err != nil { @@ -461,31 +656,84 @@ func parseStruct(parentId, edgeLabel string, parentIsStart bool, direction dsl.D for i := 0; i < slLen; i++ { relVal := relField.Index(i) - newParentId, newEdgeLabel, newParentIdStart, newDirection, newEdgeParams, followVal, skip, err := processStruct(conf, &relVal, id, parentId) + newParentId, newEdgeLabel, newParentIdStart, newDirection, newEdgeParams, followVal, followId, skip, err := processStruct(conf, &relVal, id, parentId) if err != nil { return err } + //add the current relation + if _, ok := (*curRels)[id]; !ok { + (*curRels)[id] = map[string]*RelationConfig{} + } + + if _, ok = (*curRels)[id][conf.FieldName]; !ok { + (*curRels)[id][conf.FieldName] = &RelationConfig{ + Ids: []int64{}, + UUIDs: []string{}, + RelationType: Multi, + } + } + + if followId == 0 { + var t int64 + if parentId == "" { + t = newIdRef - 1 + } else { + t = newIdRef + 1 + } + (*curRels)[id][conf.FieldName].Ids = append((*curRels)[id][conf.FieldName].Ids, t) + newIdRef-- + } else { + (*curRels)[id][conf.FieldName].Ids = append((*curRels)[id][conf.FieldName].Ids, followId) + } + + //makes us go backwards if skip { continue } - - err = parseStruct(newParentId, newEdgeLabel, newParentIdStart, newDirection, newEdgeParams, followVal, currentDepth+1, maxDepth, nodesPtr, relationsPtr, dels) + + err = parseStruct(newParentId, newEdgeLabel, newParentIdStart, newDirection, newEdgeParams, followVal, currentDepth+1, maxDepth, nodesPtr, relationsPtr, curRels, oldRels, newNodes, nodeRef, newIdRef, newIdMap) if err != nil { return err } } } else { - newParentId, newEdgeLabel, newParentIdStart, newDirection, newEdgeParams, followVal, skip, err := processStruct(conf, &relField, id, parentId) + newParentId, newEdgeLabel, newParentIdStart, newDirection, newEdgeParams, followVal, followId, skip, err := processStruct(conf, &relField, id, parentId) if err != nil { return err } + //add the current relation + if _, ok := (*curRels)[id]; !ok { + (*curRels)[id] = map[string]*RelationConfig{} + } + + if _, ok = (*curRels)[id][conf.FieldName]; !ok { + (*curRels)[id][conf.FieldName] = &RelationConfig{ + Ids: []int64{}, + UUIDs: []string{}, + RelationType: Single, + } + } + + if followId == 0 { + var t int64 + if parentId == "" { + t = newIdRef - 1 + } else { + t = newIdRef + 1 + } + (*curRels)[id][conf.FieldName].Ids = append((*curRels)[id][conf.FieldName].Ids, t) + //newIdRef-- + } else { + (*curRels)[id][conf.FieldName].Ids = append((*curRels)[id][conf.FieldName].Ids, followId) + } + if skip { continue } - err = parseStruct(newParentId, newEdgeLabel, newParentIdStart, newDirection, newEdgeParams, followVal, currentDepth+1, maxDepth, nodesPtr, relationsPtr, dels) + err = parseStruct(newParentId, newEdgeLabel, newParentIdStart, newDirection, newEdgeParams, followVal, currentDepth+1, maxDepth, nodesPtr, relationsPtr, curRels, oldRels, newNodes, nodeRef, newIdRef, newIdMap) if err != nil { return err } @@ -495,22 +743,22 @@ func parseStruct(parentId, edgeLabel string, parentIsStart bool, direction dsl.D return nil } -func processStruct(fieldConf decoratorConfig, relVal *reflect.Value, id, oldParentId string) (parentId, edgeLabel string, parentIsStart bool, direction dsl.Direction, edgeParams map[string]interface{}, followVal *reflect.Value, skip bool, err error) { +func processStruct(fieldConf decoratorConfig, relVal *reflect.Value, id, oldParentId string) (parentId, edgeLabel string, parentIsStart bool, direction dsl.Direction, edgeParams map[string]interface{}, followVal *reflect.Value, followId int64, skip bool, err error) { edgeLabel = fieldConf.Relationship relValName, err := getTypeName(relVal.Type()) if err != nil { - return "", "", false, 0, nil, nil, false, err + return "", "", false, 0, nil, nil, -1, false, err } actual, ok := mappedTypes.Get(relValName) if !ok { - return "", "", false, 0, nil, nil, false, fmt.Errorf("cannot find config for %s", edgeLabel) + return "", "", false, 0, nil, nil, -1, false, fmt.Errorf("cannot find config for %s", edgeLabel) } edgeConf, ok := actual.(structDecoratorConfig) if !ok { - return "", "", false, 0, nil, nil, false, errors.New("can not cast to structDecoratorConfig") + return "", "", false, 0, nil, nil, -1, false, errors.New("can not cast to structDecoratorConfig") } if relVal.Type().Implements(edgeType) { @@ -518,7 +766,7 @@ func processStruct(fieldConf decoratorConfig, relVal *reflect.Value, id, oldPare endValSlice := relVal.MethodByName("GetEndNode").Call(nil) if len(startValSlice) == 0 || len(endValSlice) == 0 { - return "", "", false, 0, nil, nil, false, errors.New("edge is invalid, sides are not set") + return "", "", false, 0, nil, nil, -1, false, errors.New("edge is invalid, sides are not set") } startId := reflect.Indirect(startValSlice[0].Elem()).FieldByName("UUID").String() @@ -526,7 +774,7 @@ func processStruct(fieldConf decoratorConfig, relVal *reflect.Value, id, oldPare params, err := toCypherParamsMap(*relVal, edgeConf) if err != nil { - return "", "", false, 0, nil, nil, false, err + return "", "", false, 0, nil, nil, -1, false, err } //if its nil, just default it @@ -536,37 +784,72 @@ func processStruct(fieldConf decoratorConfig, relVal *reflect.Value, id, oldPare if startId == id { + //follow the end + retVal := endValSlice[0].Elem() + + Iid := reflect.Indirect(retVal).FieldByName("Id").Interface() + + followId, ok := Iid.(int64) + if !ok { + followId = 0 + } + //check that we're not going in circles if oldParentId != "" { if endId == oldParentId { - return "", "", false, 0, nil, &reflect.Value{}, true, nil + return "", "", false, 0, nil, &reflect.Value{}, followId, true, nil } } - //follow the end - retVal := endValSlice[0].Elem() - return startId, edgeLabel, true, fieldConf.Direction, params, &retVal, false, nil + return startId, edgeLabel, true, fieldConf.Direction, params, &retVal, followId, false, nil } else if endId == id { ///follow the start retVal := startValSlice[0].Elem() + + Iid := reflect.Indirect(retVal).FieldByName("Id").Interface() + + followId, ok := Iid.(int64) + if !ok { + followId = 0 + } + if oldParentId != "" { if startId == oldParentId { - return "", "", false, 0, nil, &reflect.Value{}, true, nil + return "", "", false, 0, nil, &reflect.Value{}, followId, true, nil } } - return endId, edgeLabel, false, fieldConf.Direction, params, &retVal, false, nil + + return endId, edgeLabel, false, fieldConf.Direction, params, &retVal, followId, false, nil } else { - return "", "", false, 0, nil, nil, false, errors.New("edge is invalid, doesn't point to parent vertex") + return "", "", false, 0, nil, nil, -1, false, errors.New("edge is invalid, doesn't point to parent vertex") } } else { + var followId int64 + if oldParentId != "" { if relVal.Kind() == reflect.Ptr { *relVal = relVal.Elem() } + + Iid := reflect.Indirect(*relVal).FieldByName("Id").Interface() + + followId, ok = Iid.(int64) + if !ok { + followId = 0 + } + if relVal.FieldByName("UUID").String() == oldParentId { - return "", "", false, 0, nil, &reflect.Value{}, true, nil + return "", "", false, 0, nil, &reflect.Value{}, followId, true, nil + } + } else { + Iid := reflect.Indirect(*relVal).FieldByName("Id").Interface() + + followId, ok = Iid.(int64) + if !ok { + followId = 0 } } - return id, edgeLabel, fieldConf.Direction == dsl.DirectionOutgoing, fieldConf.Direction, map[string]interface{}{}, relVal, false, nil + + return id, edgeLabel, fieldConf.Direction == dsl.DirectionOutgoing, fieldConf.Direction, map[string]interface{}{}, relVal, followId, false, nil } } diff --git a/save_test.go b/save_test.go index a7ec722..c7b342a 100644 --- a/save_test.go +++ b/save_test.go @@ -27,13 +27,27 @@ func parseO2O(req *require.Assertions) { TestTypeDefString: "dasdfas", TestTypeDefInt: 600, BaseNode: BaseNode{ - Id: 1, + Id: 1, + UUID: "comp1uuid", + LoadMap: map[string]*RelationConfig{ + "SingleSpecA": { + Ids: []int64{2}, + RelationType: Single, + }, + }, }, } b1 := &b{ BaseNode: BaseNode{ - Id: 2, + Id: 2, + UUID: "b1uuid", + LoadMap: map[string]*RelationConfig{ + "SingleSpec": { + Ids: []int64{1}, + RelationType: Single, + }, + }, }, TestField: "test", } @@ -49,15 +63,25 @@ func parseO2O(req *require.Assertions) { nodes := map[string]map[string]nodeCreateConf{} relations := map[string][]relCreateConf{} + oldRels := map[string]map[string]*RelationConfig{} + curRels := map[string]map[string]*RelationConfig{} + ids := []*string{} val := reflect.ValueOf(comp1) + nodeRef := map[string]*reflect.Value{} - err := parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations, nil) + err := parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations, &curRels, &oldRels, &ids, &nodeRef, -1, nil) req.Nil(err) req.Equal(2, len(nodes)) req.Equal(1, len(nodes["a"])) req.Equal(1, len(nodes["b"])) req.Equal(1, len(relations)) + + req.Equal(2, len(oldRels)) + req.Equal(2, len(curRels)) + req.Equal(int64(2), curRels["comp1uuid"]["SingleSpecA"].Ids[0]) + req.Equal(int64(1), curRels["b1uuid"]["SingleSpec"].Ids[0]) + req.EqualValues(oldRels, curRels) } func parseM2O(req *require.Assertions) { @@ -67,7 +91,14 @@ func parseM2O(req *require.Assertions) { TestTypeDefString: "dasdfas", TestTypeDefInt: 600, BaseNode: BaseNode{ - Id: 1, + Id: 1, + UUID: "a1uuid", + LoadMap: map[string]*RelationConfig{ + "ManyA": { + Ids: []int64{2}, + RelationType: Multi, + }, + }, }, ManyA: []*b{}, } @@ -75,7 +106,14 @@ func parseM2O(req *require.Assertions) { b1 := &b{ TestField: "test", BaseNode: BaseNode{ - Id: 2, + Id: 2, + UUID: "b1uuid", + LoadMap: map[string]*RelationConfig{ + "ManyB" : { + Ids: []int64{1}, + RelationType: Single, + }, + }, }, } @@ -84,15 +122,19 @@ func parseM2O(req *require.Assertions) { nodes := map[string]map[string]nodeCreateConf{} relations := map[string][]relCreateConf{} + oldRels := map[string]map[string]*RelationConfig{} + curRels := map[string]map[string]*RelationConfig{} + ids := []*string{} val := reflect.ValueOf(a1) - - err := parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations, nil) + nodeRef := map[string]*reflect.Value{} + err := parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations, &curRels, &oldRels, &ids, &nodeRef, -1, nil) req.Nil(err) req.Equal(2, len(nodes)) req.Equal(1, len(nodes["a"])) req.Equal(1, len(nodes["b"])) req.Equal(1, len(relations)) + req.EqualValues(oldRels, curRels) } func parseM2M(req *require.Assertions) { @@ -102,7 +144,14 @@ func parseM2M(req *require.Assertions) { TestTypeDefString: "dasdfas", TestTypeDefInt: 600, BaseNode: BaseNode{ - Id: 1, + Id: 1, + UUID: "a1uuid", + LoadMap: map[string]*RelationConfig{ + "MultiA": { + Ids: []int64{2}, + RelationType: Multi, + }, + }, }, ManyA: []*b{}, MultiA: []*b{}, @@ -111,7 +160,14 @@ func parseM2M(req *require.Assertions) { b1 := &b{ TestField: "test", BaseNode: BaseNode{ - Id: 2, + Id: 2, + UUID: "b1uuid", + LoadMap: map[string]*RelationConfig{ + "Multi": { + Ids: []int64{1}, + RelationType: Multi, + }, + }, }, Multi: []*a{}, } @@ -121,46 +177,191 @@ func parseM2M(req *require.Assertions) { nodes := map[string]map[string]nodeCreateConf{} relations := map[string][]relCreateConf{} + oldRels := map[string]map[string]*RelationConfig{} + curRels := map[string]map[string]*RelationConfig{} + ids := []*string{} + + nodeRef := map[string]*reflect.Value{} val := reflect.ValueOf(a1) - err := parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations, nil) + err := parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations, &curRels, &oldRels, &ids, &nodeRef, -1, nil) req.Nil(err) req.Equal(2, len(nodes)) req.Equal(1, len(nodes["a"])) req.Equal(1, len(nodes["b"])) req.Equal(1, len(relations)) + req.EqualValues(oldRels, curRels) +} + +func TestCalculateDels(t *testing.T) { + req := require.New(t) + + //test node removed + dels := calculateDels(map[string]map[string]*RelationConfig{ + "node1": { + "RelField": { + Ids: []int64{2}, + RelationType: Single, + }, + }, + "node2": { + "RelField2": { + Ids: []int64{1}, + RelationType: Single, + }, + }, + }, map[string]map[string]*RelationConfig{ + "node1": { + "RelField": { + Ids: []int64{}, + RelationType: Single, + }, + }, + }) + + req.EqualValues(map[string][]int64{ + "node1": {2}, + }, dels) + + //test field removed + dels = calculateDels(map[string]map[string]*RelationConfig{ + "node1": { + "RelField": { + Ids: []int64{2}, + RelationType: Single, + }, + }, + "node2": { + "RelField2": { + Ids: []int64{1}, + RelationType: Single, + }, + }, + }, map[string]map[string]*RelationConfig{ + "node1": { + "RelField": { + Ids: []int64{}, + RelationType: Single, + }, + }, + "node2": { + "RelFieldNew": { + Ids: []int64{}, + RelationType: Single, + }, + }, + }) + + req.EqualValues(map[string][]int64{ + "node1": {2}, + "node2": {1}, + }, dels) + + //test field empty + dels = calculateDels(map[string]map[string]*RelationConfig{ + "node1": { + "RelField": { + Ids: []int64{2}, + RelationType: Single, + }, + }, + "node2": { + "RelField2": { + Ids: []int64{1}, + RelationType: Single, + }, + }, + }, map[string]map[string]*RelationConfig{ + "node1": { + "RelField": { + Ids: []int64{}, + RelationType: Single, + }, + }, + "node2": { + "RelField2": { + Ids: []int64{}, + RelationType: Single, + }, + }, + }) + + req.EqualValues(map[string][]int64{ + "node1": {2}, + "node2": {1}, + }, dels) + + //test nothing changed + dels = calculateDels(map[string]map[string]*RelationConfig{ + "node1": { + "RelField": { + Ids: []int64{2}, + RelationType: Single, + }, + }, + "node2": { + "RelField2": { + Ids: []int64{1}, + RelationType: Single, + }, + }, + }, map[string]map[string]*RelationConfig{ + "node1": { + "RelField": { + Ids: []int64{2}, + RelationType: Single, + }, + }, + "node2": { + "RelField2": { + Ids: []int64{1}, + RelationType: Single, + }, + }, + }) + + req.EqualValues(map[string][]int64{}, dels) } func TestSave(t *testing.T) { - t.Skip() + //t.Skip() req := require.New(t) - req.Nil(setupInit(true, nil, &a{}, &b{}, &c{})) + conf := Config{ + Username: "neo4j", + Password: "password", + Host: "0.0.0.0", + Port: 7687, + PoolSize: 15, + IndexStrategy: IGNORE_INDEX, + } + + req.Nil(Init(&conf, &a{}, &b{}, &c{})) - comp2 := &a{ + a2 := &a{ TestField: "test", - BaseNode: BaseNode{ - Id: 1, - }, } b2 := &b{ TestField: "test", TestTime: time.Now().UTC(), - BaseNode: BaseNode{ - Id: 2, - }, + } + + b3 := &b{ + TestField: "dasdfasd", } c1 := &c{ - Start: comp2, + Start: a2, End: b2, Test: "testing", } - comp2.SingleSpecA = c1 + a2.SingleSpecA = c1 + a2.ManyA = []*b{b3} b2.SingleSpec = c1 + b3.ManyB = a2 conn, err := driverPool.Open(driver.ReadWriteMode) if err != nil { @@ -168,5 +369,36 @@ func TestSave(t *testing.T) { } defer driverPool.Reclaim(conn) - req.Nil(saveDepth(conn, comp2, defaultSaveDepth)) + req.Nil(saveDepth(conn, a2, 5)) + req.EqualValues(map[string]*RelationConfig{ + "SingleSpecA": { + Ids: []int64{b2.Id}, + UUIDs: []string{}, + RelationType: Single, + }, + "ManyA": { + Ids: []int64{b3.Id}, + UUIDs: []string{}, + RelationType: Multi, + }, + }, a2.LoadMap) + req.EqualValues(map[string]*RelationConfig{ + "SingleSpec": { + Ids: []int64{a2.Id}, + UUIDs: []string{}, + RelationType: Single, + }, + }, b2.LoadMap) + req.EqualValues(map[string]*RelationConfig{ + "ManyB": { + Ids: []int64{a2.Id}, + UUIDs: []string{}, + RelationType: Single, + }, + }, b3.LoadMap) + a2.SingleSpecA = nil + b2.SingleSpec = nil + + req.Nil(saveDepth(conn, a2, defaultSaveDepth)) + log.Println("done") } diff --git a/testing_/linking.go b/testing_/linking.go index da3c35f..16a1a6b 100644 --- a/testing_/linking.go +++ b/testing_/linking.go @@ -6,6 +6,66 @@ import ( ) +func(l *ExampleObject) LinkToExampleObject2OnFieldSpecial(target *ExampleObject2, edge *SpecialEdge) error { + if target == nil { + return errors.New("start and end can not be nil") + } + + if edge == nil { + return errors.New("edge can not be nil") + } + + err := edge.SetStartNode(l) + if err != nil { + return err + } + + err = edge.SetEndNode(target) + if err != nil { + return err + } + + l.Special = edge + + if target.Special == nil { + target.Special = make([]*SpecialEdge, 1, 1) + target.Special[0] = edge + } else { + target.Special = append(target.Special, edge) + } + + return nil +} + +func(l *ExampleObject) UnlinkFromExampleObject2OnFieldSpecial(target *ExampleObject2) error { + if target == nil { + return errors.New("start and end can not be nil") + } + + l.Special = nil + + if target.Special != nil { + for i, unlinkTarget := range target.Special { + + obj := unlinkTarget.GetStartNode() + + checkObj, ok := obj.(*ExampleObject) + if !ok { + return errors.New("unable to cast unlinkTarget to [ExampleObject]") + } + if checkObj.UUID == l.UUID { + a := &target.Special + (*a)[i] = (*a)[len(*a)-1] + (*a)[len(*a)-1] = nil + *a = (*a)[:len(*a)-1] + break + } + } + } + + return nil +} + func(l *ExampleObject) LinkToExampleObjectOnFieldChildren(targets ...*ExampleObject) error { if targets == nil { return errors.New("start and end can not be nil") @@ -87,9 +147,9 @@ func(l *ExampleObject) UnlinkFromExampleObjectOnFieldParents(target *ExampleObje } return nil -} +} -func(l *ExampleObject) LinkToExampleObject2OnFieldSpecial(target *ExampleObject2, edge *SpecialEdge) error { +func(l *ExampleObject2) LinkToExampleObjectOnFieldSpecial(target *ExampleObject, edge *SpecialEdge) error { if target == nil { return errors.New("start and end can not be nil") } @@ -98,46 +158,44 @@ func(l *ExampleObject) LinkToExampleObject2OnFieldSpecial(target *ExampleObject2 return errors.New("edge can not be nil") } - err := edge.SetStartNode(l) + err := edge.SetStartNode(target) if err != nil { return err } - err = edge.SetEndNode(target) + err = edge.SetEndNode(l) if err != nil { return err } - l.Special = edge - - if target.Special == nil { - target.Special = make([]*SpecialEdge, 1, 1) - target.Special[0] = edge + if l.Special == nil { + l.Special = make([]*SpecialEdge, 1, 1) + l.Special[0] = edge } else { - target.Special = append(target.Special, edge) + l.Special = append(l.Special, edge) } + + target.Special = edge return nil } -func(l *ExampleObject) UnlinkFromExampleObject2OnFieldSpecial(target *ExampleObject2) error { +func(l *ExampleObject2) UnlinkFromExampleObjectOnFieldSpecial(target *ExampleObject) error { if target == nil { return errors.New("start and end can not be nil") } - l.Special = nil - - if target.Special != nil { - for i, unlinkTarget := range target.Special { + if l.Special != nil { + for i, unlinkTarget := range l.Special { - obj := unlinkTarget.GetStartNode() + obj := unlinkTarget.GetEndNode() checkObj, ok := obj.(*ExampleObject) if !ok { return errors.New("unable to cast unlinkTarget to [ExampleObject]") } - if checkObj.UUID == l.UUID { - a := &target.Special + if checkObj.UUID == target.UUID { + a := &l.Special (*a)[i] = (*a)[len(*a)-1] (*a)[len(*a)-1] = nil *a = (*a)[:len(*a)-1] @@ -145,9 +203,11 @@ func(l *ExampleObject) UnlinkFromExampleObject2OnFieldSpecial(target *ExampleObj } } } + + target.Special = nil return nil -} +} func(l *ExampleObject2) LinkToExampleObject2OnFieldChildren2(targets ...*ExampleObject2) error { if targets == nil { @@ -229,65 +289,5 @@ func(l *ExampleObject2) UnlinkFromExampleObject2OnFieldParents2(target *ExampleO } } - return nil -} - -func(l *ExampleObject2) LinkToExampleObjectOnFieldSpecial(target *ExampleObject, edge *SpecialEdge) error { - if target == nil { - return errors.New("start and end can not be nil") - } - - if edge == nil { - return errors.New("edge can not be nil") - } - - err := edge.SetStartNode(target) - if err != nil { - return err - } - - err = edge.SetEndNode(l) - if err != nil { - return err - } - - if l.Special == nil { - l.Special = make([]*SpecialEdge, 1, 1) - l.Special[0] = edge - } else { - l.Special = append(l.Special, edge) - } - - target.Special = edge - - return nil -} - -func(l *ExampleObject2) UnlinkFromExampleObjectOnFieldSpecial(target *ExampleObject) error { - if target == nil { - return errors.New("start and end can not be nil") - } - - if l.Special != nil { - for i, unlinkTarget := range l.Special { - - obj := unlinkTarget.GetEndNode() - - checkObj, ok := obj.(*ExampleObject) - if !ok { - return errors.New("unable to cast unlinkTarget to [ExampleObject]") - } - if checkObj.UUID == target.UUID { - a := &l.Special - (*a)[i] = (*a)[len(*a)-1] - (*a)[len(*a)-1] = nil - *a = (*a)[:len(*a)-1] - break - } - } - } - - target.Special = nil - return nil } \ No newline at end of file diff --git a/testing_/linking_test.go b/testing_/linking_test.go index 2aeacc9..ada6815 100644 --- a/testing_/linking_test.go +++ b/testing_/linking_test.go @@ -16,7 +16,7 @@ func TestLinking(t *testing.T) { BaseNode: gogm.BaseNode{ Id: 0, UUID: id1, - LoadMap: map[string]*gogm.RelationLoad{}, + LoadMap: map[string]*gogm.RelationConfig{}, }, } @@ -24,7 +24,7 @@ func TestLinking(t *testing.T) { BaseNode: gogm.BaseNode{ Id: 1, UUID: id2, - LoadMap: map[string]*gogm.RelationLoad{}, + LoadMap: map[string]*gogm.RelationConfig{}, }, } diff --git a/util.go b/util.go index bc0784f..660af09 100644 --- a/util.go +++ b/util.go @@ -20,6 +20,15 @@ func int64SliceContains(s []int64, e int64) bool { return false } +func stringSliceContains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} + func setUuidIfNeeded(val *reflect.Value, fieldName string) (bool, string, error) { if val == nil { return false, "", errors.New("value can not be nil") From e3f9ba264713bf08d84f4c1a35846ab754f1f0df Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Tue, 3 Dec 2019 13:02:34 -0500 Subject: [PATCH 09/27] it works --- defaults.go | 2 - save.go | 310 +++++++++++++++++++++++++++------------------------ save_test.go | 12 +- 3 files changed, 168 insertions(+), 156 deletions(-) diff --git a/defaults.go b/defaults.go index a1560b0..57b2152 100644 --- a/defaults.go +++ b/defaults.go @@ -18,7 +18,5 @@ const ( type RelationConfig struct { Ids []int64 `json:"-" gomg:"-"` - //used to replace for new nodes - UUIDs []string `json:"-"` RelationType RelationType `json:"-" gomg:"-"` } diff --git a/save.go b/save.go index 916e774..e0e3412 100644 --- a/save.go +++ b/save.go @@ -30,6 +30,7 @@ type relDelConf struct { EndNodeId int64 } +//todo optimize func saveDepth(sess *driver.BoltConn, obj interface{}, depth int) error { if sess == nil { return errors.New("session can not be nil") @@ -70,8 +71,6 @@ func saveDepth(sess *driver.BoltConn, obj interface{}, depth int) error { // node id -- [field] config oldRels := map[string]map[string]*RelationConfig{} curRels := map[string]map[string]*RelationConfig{} - newIdRef := int64(-1) - newIdMap := map[int64]string{} // uuid -> reflect value nodeRef := map[string]*reflect.Value{} @@ -80,63 +79,28 @@ func saveDepth(sess *driver.BoltConn, obj interface{}, depth int) error { rootVal := reflect.ValueOf(obj) - err := parseStruct("", "", false, 0, nil, &rootVal, 0, depth, &nodes, &relations, &curRels, &oldRels, &newNodes, &nodeRef, newIdRef, &newIdMap) + err := parseStruct("", "", false, 0, nil, &rootVal, 0, depth, &nodes, &relations, &oldRels, &newNodes, &nodeRef) if err != nil { return err } - ids, err := createNodes(sess, nodes) + ids, err := createNodes(sess, nodes, &nodeRef) + if err != nil { + return err + } + + err = generateCurRels("", &rootVal, 0, depth, &curRels) if err != nil { return err } - //calculate dels dels := calculateDels(oldRels, curRels) var wg sync.WaitGroup var err1, err2, err3 error //fix the cur rels and write them to their perspective nodes wg.Add(1) - go func(wg *sync.WaitGroup, _curRels *map[string]map[string]*RelationConfig, _nodeRef *map[string]*reflect.Value, _ids *map[string]int64, _newIdMap *map[int64]string, _err *error) { - if len(newNodes) != 0 { - for _, relConfs := range *_curRels { - for _, relConf := range relConfs { - //if len(relConf.UUIDs) != 0 { - // for _, uuid := range relConf.UUIDs { - // if id, ok := (*_ids)[uuid]; ok { - // relConf.Ids = append(relConf.Ids, id) - // } else { - // _err = fmt.Errorf("id not found for node [%s]", uuid) - // return - // } - // } - //} - - //todo make this more efficient - for i, id := range relConf.Ids { - if id < 0 { - if uuid, ok := (*_newIdMap)[id]; ok { - if actualId, ok := (*_ids)[uuid]; ok { - relConf.Ids[i] = actualId - } else { - *_err = fmt.Errorf("actual id not found for uuid [%s]", uuid) - wg.Done() - return - } - } else { - *_err = fmt.Errorf("uuid not found for holder id [%v]", id) - wg.Done() - return - } - } - } - - //reset uuids - relConf.UUIDs = []string{} - } - } - } - + go func(wg *sync.WaitGroup, _curRels *map[string]map[string]*RelationConfig, _nodeRef *map[string]*reflect.Value, _ids *map[string]int64, _err *error) { for uuid, val := range *_nodeRef { loadConf, ok := (*_curRels)[uuid] if !ok { @@ -150,20 +114,14 @@ func saveDepth(sess *driver.BoltConn, obj interface{}, depth int) error { *val = val.Elem() } - id, ok := (*_ids)[uuid] - if !ok { - *_err = fmt.Errorf("GraphId not found for node [%s]", uuid) - wg.Done() - return - } - reflect.Indirect(*val).FieldByName("LoadMap").Set(reflect.ValueOf(loadConf)) - reflect.Indirect(*val).FieldByName("Id").Set(reflect.ValueOf(id)) } + wg.Done() - }(&wg, &curRels, &nodeRef, &ids, &newIdMap, &err3) + }(&wg, &curRels, &nodeRef, &ids, &err3) //execute concurrently + //calculate dels if len(dels) != 0 { wg.Add(1) @@ -263,7 +221,7 @@ func removeRelations(conn *driver.BoltConn, dels map[string][]int64) error { } startParams, err := dsl.ParamsFromMap(map[string]interface{}{ - "uuid": dsl.ParamString("{row.startNodeId}"), + "uuid": dsl.ParamString("row.startNodeId"), }) if err != nil { return fmt.Errorf("%s, %w", err.Error(), ErrInternal) @@ -280,7 +238,7 @@ func removeRelations(conn *driver.BoltConn, dels map[string][]int64) error { }).V(dsl.V{ Name: "end", }).Build()). - Cypher("id(end) IN row.endNodeIds"). + Cypher("WHERE id(end) IN row.endNodeIds"). Delete(false, "e"). WithNeo(conn). Exec(map[string]interface{}{ @@ -300,7 +258,7 @@ func removeRelations(conn *driver.BoltConn, dels map[string][]int64) error { } } -func createNodes(conn *driver.BoltConn, crNodes map[string]map[string]nodeCreateConf) (map[string]int64, error) { +func createNodes(conn *driver.BoltConn, crNodes map[string]map[string]nodeCreateConf, nodeRef *map[string]*reflect.Value) (map[string]int64, error) { idMap := map[string]int64{} for label, nodes := range crNodes { @@ -365,7 +323,24 @@ func createNodes(conn *driver.BoltConn, crNodes map[string]map[string]nodeCreate continue } - idMap[row[0].(string)] = row[1].(int64) + uuid, ok := row[0].(string) + if !ok { + return nil, fmt.Errorf("cannot cast row[0] to string, %w", ErrInternal) + } + + graphId, ok := row[1].(int64) + if !ok { + return nil, fmt.Errorf("cannot cast row[1] to int64, %w", ErrInternal) + } + + idMap[uuid] = graphId + //set the new id + val, ok := (*nodeRef)[uuid] + if !ok { + return nil, fmt.Errorf("cannot find val for uuid [%s]", uuid) + } + + reflect.Indirect(*val).FieldByName("Id").Set(reflect.ValueOf(graphId)) } err = res.Close() @@ -493,9 +468,131 @@ func parseValidate(currentDepth, maxDepth int, current *reflect.Value, nodesPtr return nil } +func generateCurRels(parentId string, current *reflect.Value, currentDepth, maxDepth int, curRels *map[string]map[string]*RelationConfig) error { + if currentDepth > maxDepth { + return nil + } + + uuid := reflect.Indirect(*current).FieldByName("UUID").String() + if uuid == "" { + return errors.New("uuid not set") + } + + if _, ok := (*curRels)[uuid]; ok { + //this node has already been seen + return nil + } + + //id := reflect.Indirect(*current).FieldByName("UUID").Interface().(int64) + + //get the type + tString, err := getTypeName(current.Type()) + if err != nil { + return err + } + + //get the config + actual, ok := mappedTypes.Get(tString) + if !ok { + return fmt.Errorf("struct config not found type (%s)", tString) + } + + //cast the config + currentConf, ok := actual.(structDecoratorConfig) + if !ok { + return errors.New("unable to cast into struct decorator config") + } + for _, conf := range currentConf.Fields { + if conf.Relationship == "" { + continue + } + + relField := reflect.Indirect(*current).FieldByName(conf.FieldName) + + //if its nil, just skip it + if relField.IsNil() { + continue + } + + if conf.ManyRelationship { + slLen := relField.Len() + if slLen == 0 { + continue + } + + for i := 0; i < slLen; i++ { + relVal := relField.Index(i) + + newParentId, _, _, _, _, followVal, followId, _, err := processStruct(conf, &relVal, uuid, parentId) + if err != nil { + return err + } + + //makes us go backwards + //if skip { + // continue + //} + + //check that the map is there for this id + if _, ok := (*curRels)[uuid]; !ok { + (*curRels)[uuid] = map[string]*RelationConfig{} + } + + //check the config is there for the specified field + if _, ok = (*curRels)[uuid][conf.FieldName]; !ok { + (*curRels)[uuid][conf.FieldName] = &RelationConfig{ + Ids: []int64{}, + RelationType: Multi, + } + } + + (*curRels)[uuid][conf.FieldName].Ids = append((*curRels)[uuid][conf.FieldName].Ids, followId) + + err = generateCurRels(newParentId, followVal, currentDepth+1, maxDepth, curRels) + if err != nil { + return err + } + } + } else { + newParentId, _, _, _, _, followVal, followId, _, err := processStruct(conf, &relField, uuid, parentId) + if err != nil { + return err + } + + //makes us go backwards + //if skip { + // continue + //} + + //check that the map is there for this id + if _, ok := (*curRels)[uuid]; !ok { + (*curRels)[uuid] = map[string]*RelationConfig{} + } + + //check the config is there for the specified field + if _, ok = (*curRels)[uuid][conf.FieldName]; !ok { + (*curRels)[uuid][conf.FieldName] = &RelationConfig{ + Ids: []int64{}, + RelationType: Single, + } + } + + (*curRels)[uuid][conf.FieldName].Ids = append((*curRels)[uuid][conf.FieldName].Ids, followId) + + err = generateCurRels(newParentId, followVal, currentDepth+1, maxDepth, curRels) + if err != nil { + return err + } + + } + } + + return nil +} + func parseStruct(parentId, edgeLabel string, parentIsStart bool, direction dsl.Direction, edgeParams map[string]interface{}, current *reflect.Value, - currentDepth int, maxDepth int, nodesPtr *map[string]map[string]nodeCreateConf, relationsPtr *map[string][]relCreateConf, curRels , oldRels *map[string]map[string]*RelationConfig, - newNodes *[]*string, nodeRef *map[string]*reflect.Value, newIdRef int64, newIdMap *map[int64]string) error { + currentDepth, maxDepth int, nodesPtr *map[string]map[string]nodeCreateConf, relationsPtr *map[string][]relCreateConf, oldRels *map[string]map[string]*RelationConfig, + newNodes *[]*string, nodeRef *map[string]*reflect.Value) error { //check if its done if currentDepth > maxDepth { return nil @@ -533,9 +630,6 @@ func parseStruct(parentId, edgeLabel string, parentIsStart bool, direction dsl.D return err } - // set old rels to current load map of the node - (*curRels)[id] = map[string]*RelationConfig{} - if !isNewNode { if _, ok := (*oldRels)[id]; !ok{ iConf := reflect.Indirect(*current).FieldByName("LoadMap").Interface() @@ -552,30 +646,6 @@ func parseStruct(parentId, edgeLabel string, parentIsStart bool, direction dsl.D (*oldRels)[id] = relConf } } else { - //if parentField != "" { - // if _, ok := (*curRels)[parentId]; !ok { - // (*curRels)[parentId] = map[string]*RelationConfig{} - // } - // - // if (*curRels)[parentId][parentField] == nil { - // (*curRels)[parentId][parentField] = &RelationConfig{ - // Ids: []int64{}, - // UUIDs: []string{}, - // RelationType: 0, - // } - // } - // (*curRels)[parentId][parentField].UUIDs = append((*curRels)[parentId][parentField].UUIDs, id) - //} - //this was already set i guess - for { - if _, ok :=(*newIdMap)[newIdRef]; ok { - newIdRef-- - } else { - break - } - } - - (*newIdMap)[newIdRef] = id *newNodes = append(*newNodes, &id) } @@ -656,84 +726,32 @@ func parseStruct(parentId, edgeLabel string, parentIsStart bool, direction dsl.D for i := 0; i < slLen; i++ { relVal := relField.Index(i) - newParentId, newEdgeLabel, newParentIdStart, newDirection, newEdgeParams, followVal, followId, skip, err := processStruct(conf, &relVal, id, parentId) + newParentId, newEdgeLabel, newParentIdStart, newDirection, newEdgeParams, followVal, _, skip, err := processStruct(conf, &relVal, id, parentId) if err != nil { return err } - //add the current relation - if _, ok := (*curRels)[id]; !ok { - (*curRels)[id] = map[string]*RelationConfig{} - } - - if _, ok = (*curRels)[id][conf.FieldName]; !ok { - (*curRels)[id][conf.FieldName] = &RelationConfig{ - Ids: []int64{}, - UUIDs: []string{}, - RelationType: Multi, - } - } - - if followId == 0 { - var t int64 - if parentId == "" { - t = newIdRef - 1 - } else { - t = newIdRef + 1 - } - (*curRels)[id][conf.FieldName].Ids = append((*curRels)[id][conf.FieldName].Ids, t) - newIdRef-- - } else { - (*curRels)[id][conf.FieldName].Ids = append((*curRels)[id][conf.FieldName].Ids, followId) - } - //makes us go backwards if skip { continue } - err = parseStruct(newParentId, newEdgeLabel, newParentIdStart, newDirection, newEdgeParams, followVal, currentDepth+1, maxDepth, nodesPtr, relationsPtr, curRels, oldRels, newNodes, nodeRef, newIdRef, newIdMap) + err = parseStruct(newParentId, newEdgeLabel, newParentIdStart, newDirection, newEdgeParams, followVal, currentDepth+1, maxDepth, nodesPtr, relationsPtr, oldRels, newNodes, nodeRef) if err != nil { return err } } } else { - newParentId, newEdgeLabel, newParentIdStart, newDirection, newEdgeParams, followVal, followId, skip, err := processStruct(conf, &relField, id, parentId) + newParentId, newEdgeLabel, newParentIdStart, newDirection, newEdgeParams, followVal, _, skip, err := processStruct(conf, &relField, id, parentId) if err != nil { return err } - //add the current relation - if _, ok := (*curRels)[id]; !ok { - (*curRels)[id] = map[string]*RelationConfig{} - } - - if _, ok = (*curRels)[id][conf.FieldName]; !ok { - (*curRels)[id][conf.FieldName] = &RelationConfig{ - Ids: []int64{}, - UUIDs: []string{}, - RelationType: Single, - } - } - - if followId == 0 { - var t int64 - if parentId == "" { - t = newIdRef - 1 - } else { - t = newIdRef + 1 - } - (*curRels)[id][conf.FieldName].Ids = append((*curRels)[id][conf.FieldName].Ids, t) - //newIdRef-- - } else { - (*curRels)[id][conf.FieldName].Ids = append((*curRels)[id][conf.FieldName].Ids, followId) - } - if skip { continue } - err = parseStruct(newParentId, newEdgeLabel, newParentIdStart, newDirection, newEdgeParams, followVal, currentDepth+1, maxDepth, nodesPtr, relationsPtr, curRels, oldRels, newNodes, nodeRef, newIdRef, newIdMap) + err = parseStruct(newParentId, newEdgeLabel, newParentIdStart, newDirection, newEdgeParams, followVal, currentDepth+1, maxDepth, nodesPtr, relationsPtr, oldRels, newNodes, nodeRef) if err != nil { return err } @@ -797,7 +815,7 @@ func processStruct(fieldConf decoratorConfig, relVal *reflect.Value, id, oldPare //check that we're not going in circles if oldParentId != "" { if endId == oldParentId { - return "", "", false, 0, nil, &reflect.Value{}, followId, true, nil + return startId, edgeLabel, true, fieldConf.Direction, params, &retVal, followId, true, nil } } @@ -815,7 +833,7 @@ func processStruct(fieldConf decoratorConfig, relVal *reflect.Value, id, oldPare if oldParentId != "" { if startId == oldParentId { - return "", "", false, 0, nil, &reflect.Value{}, followId, true, nil + return endId, edgeLabel, false, fieldConf.Direction, params, &retVal, followId, true, nil } } @@ -839,7 +857,7 @@ func processStruct(fieldConf decoratorConfig, relVal *reflect.Value, id, oldPare } if relVal.FieldByName("UUID").String() == oldParentId { - return "", "", false, 0, nil, &reflect.Value{}, followId, true, nil + return id, edgeLabel, fieldConf.Direction == dsl.DirectionOutgoing, fieldConf.Direction, map[string]interface{}{}, relVal, followId, true, nil } } else { Iid := reflect.Indirect(*relVal).FieldByName("Id").Interface() diff --git a/save_test.go b/save_test.go index c7b342a..ce48709 100644 --- a/save_test.go +++ b/save_test.go @@ -70,7 +70,7 @@ func parseO2O(req *require.Assertions) { val := reflect.ValueOf(comp1) nodeRef := map[string]*reflect.Value{} - err := parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations, &curRels, &oldRels, &ids, &nodeRef, -1, nil) + err := parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations, &oldRels, &ids, &nodeRef) req.Nil(err) req.Equal(2, len(nodes)) req.Equal(1, len(nodes["a"])) @@ -128,7 +128,7 @@ func parseM2O(req *require.Assertions) { val := reflect.ValueOf(a1) nodeRef := map[string]*reflect.Value{} - err := parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations, &curRels, &oldRels, &ids, &nodeRef, -1, nil) + err := parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations, &oldRels, &ids, &nodeRef) req.Nil(err) req.Equal(2, len(nodes)) req.Equal(1, len(nodes["a"])) @@ -185,7 +185,7 @@ func parseM2M(req *require.Assertions) { val := reflect.ValueOf(a1) - err := parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations, &curRels, &oldRels, &ids, &nodeRef, -1, nil) + err := parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations, &oldRels, &ids, &nodeRef) req.Nil(err) req.Equal(2, len(nodes)) req.Equal(1, len(nodes["a"])) @@ -373,32 +373,28 @@ func TestSave(t *testing.T) { req.EqualValues(map[string]*RelationConfig{ "SingleSpecA": { Ids: []int64{b2.Id}, - UUIDs: []string{}, RelationType: Single, }, "ManyA": { Ids: []int64{b3.Id}, - UUIDs: []string{}, RelationType: Multi, }, }, a2.LoadMap) req.EqualValues(map[string]*RelationConfig{ "SingleSpec": { Ids: []int64{a2.Id}, - UUIDs: []string{}, RelationType: Single, }, }, b2.LoadMap) req.EqualValues(map[string]*RelationConfig{ "ManyB": { Ids: []int64{a2.Id}, - UUIDs: []string{}, RelationType: Single, }, }, b3.LoadMap) a2.SingleSpecA = nil b2.SingleSpec = nil - req.Nil(saveDepth(conn, a2, defaultSaveDepth)) + req.Nil(saveDepth(conn, a2, 5)) log.Println("done") } From ff7de1e3e51a9c57fc18a0911a2a5817d1736ba7 Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Tue, 3 Dec 2019 13:02:58 -0500 Subject: [PATCH 10/27] gofmt --- cmd/gogmcli/gen/gen.go | 16 +++---- cmd/gogmcli/gen/parse.go | 24 +++++----- cmd/gogmcli/gen/templ.go | 15 +++---- cmd/gogmcli/gogm.go | 2 +- decoder.go | 6 +-- decoder_test.go | 8 ++-- decorator.go | 4 +- decorator_test.go | 4 +- save.go | 30 ++++++------- save_test.go | 84 +++++++++++++++++----------------- setup.go | 2 +- testing_/linking.go | 97 ++++++++++++++++++++-------------------- testing_/linking_test.go | 2 +- testing_/test_edge.go | 4 +- testing_/test_obj.go | 4 +- testing_/test_obj2.go | 4 +- util.go | 10 ++--- 17 files changed, 156 insertions(+), 160 deletions(-) diff --git a/cmd/gogmcli/gen/gen.go b/cmd/gogmcli/gen/gen.go index 7c01675..1dfdffa 100644 --- a/cmd/gogmcli/gen/gen.go +++ b/cmd/gogmcli/gen/gen.go @@ -28,7 +28,7 @@ func Generate(directory string, debug bool) error { return errors.New("file info is nil") } - if info.IsDir() && path != directory{ + if info.IsDir() && path != directory { if debug { log.Printf("skipping [%s] as it is a directory\n", path) } @@ -90,7 +90,7 @@ func Generate(directory string, debug bool) error { // validate relationships (i.e even number) for name, rel := range relations { - if len(rel) % 2 != 0 { + if len(rel)%2 != 0 { return fmt.Errorf("relationship [%s] is invalid", name) } } @@ -101,10 +101,10 @@ func Generate(directory string, debug bool) error { for _, rels := range relations { for _, rel := range rels { tplRel := &tplRelConf{ - StructName: rel.NodeName, - StructField: rel.Field, - OtherStructName: rel.Type, - StructFieldIsMany: rel.IsMany, + StructName: rel.NodeName, + StructField: rel.Field, + OtherStructName: rel.Type, + StructFieldIsMany: rel.IsMany, } var isSpecialEdge bool @@ -116,10 +116,10 @@ func Generate(directory string, debug bool) error { isSpecialEdge = true } - searchLoop: + searchLoop: for _, lookup := range rels { //check special edge - if rel.Type != lookup.NodeName && !isSpecialEdge{ + if rel.Type != lookup.NodeName && !isSpecialEdge { continue } diff --git a/cmd/gogmcli/gen/parse.go b/cmd/gogmcli/gen/parse.go index 0a7fa66..e39d5b2 100644 --- a/cmd/gogmcli/gen/parse.go +++ b/cmd/gogmcli/gen/parse.go @@ -13,12 +13,12 @@ import ( ) type relConf struct { - NodeName string - Field string + NodeName string + Field string RelationshipName string - Type string - IsMany bool - Direction go_cypherdsl.Direction + Type string + IsMany bool + Direction go_cypherdsl.Direction } func parseFile(filePath string, confs *map[string][]*relConf, edges *[]string, imports map[string][]string, packageName *string) error { @@ -170,7 +170,7 @@ func parseGogmNode(strType *ast.StructType, confs *map[string][]*relConf, label for _, p := range gogmParts { if strings.Contains(p, "direction") { str := strings.ToLower(strings.Replace(strings.Replace(strings.Replace(p, "direction=", "", -1), "\"", "", -1), "`", "", -1)) - switch str{ + switch str { case "incoming": dir = go_cypherdsl.DirectionIncoming break @@ -202,12 +202,12 @@ func parseGogmNode(strType *ast.StructType, confs *map[string][]*relConf, label t := typeNameBuf.String() (*confs)[label] = append((*confs)[label], &relConf{ - Field: field.Names[0].Name, + Field: field.Names[0].Name, RelationshipName: relName, - Type: strings.Replace(strings.Replace(t, "[]", "", -1), "*", "", -1), - IsMany: strings.Contains(t, "[]"), - Direction: dir, - NodeName: label, + Type: strings.Replace(strings.Replace(t, "[]", "", -1), "*", "", -1), + IsMany: strings.Contains(t, "[]"), + Direction: dir, + NodeName: label, }) } } @@ -216,4 +216,4 @@ func parseGogmNode(strType *ast.StructType, confs *map[string][]*relConf, label } return nil -} \ No newline at end of file +} diff --git a/cmd/gogmcli/gen/templ.go b/cmd/gogmcli/gen/templ.go index b54db47..0e5160b 100644 --- a/cmd/gogmcli/gen/templ.go +++ b/cmd/gogmcli/gen/templ.go @@ -1,6 +1,5 @@ package gen - //expect .StructName .OtherStructName .StructField .OtherStructField .StructFieldIsMany .OtherStructFieldIsMany var linkSpec = ` {{ define "linkSpec" }} @@ -255,18 +254,18 @@ import ( ` type templateConfig struct { - Imports []string + Imports []string PackageName string // type: funcs Funcs map[string][]*tplRelConf } type tplRelConf struct { - StructName string - StructField string - OtherStructField string - OtherStructName string - StructFieldIsMany bool + StructName string + StructField string + OtherStructField string + OtherStructName string + StructFieldIsMany bool OtherStructFieldIsMany bool //stuff for special edges @@ -274,4 +273,4 @@ type tplRelConf struct { SpecialEdgeType string // StructName = Start if true SpecialEdgeDirection bool -} \ No newline at end of file +} diff --git a/cmd/gogmcli/gogm.go b/cmd/gogmcli/gogm.go index 7955fc5..026e25d 100644 --- a/cmd/gogmcli/gogm.go +++ b/cmd/gogmcli/gogm.go @@ -57,7 +57,7 @@ func main() { Flags: []cli.Flag{ &cli.BoolFlag{ Name: "debug", - Aliases: []string{"d"}, + Aliases: []string{"d"}, Usage: "execute in debug mode", Value: false, Destination: &debug, diff --git a/decoder.go b/decoder.go index 641f997..abcde1b 100644 --- a/decoder.go +++ b/decoder.go @@ -140,9 +140,9 @@ func decode(rawArr [][]interface{}, respObj interface{}) (err error) { if conf, ok := startMap[startConfig.FieldName]; ok { conf.Ids = append(conf.Ids, relationConfig.EndNodeId) } else { - var rt RelationType - if startConfig.ManyRelationship { - rt = Multi + var rt RelationType + if startConfig.ManyRelationship { + rt = Multi } else { rt = Single } diff --git a/decoder_test.go b/decoder_test.go index 330193e..7c94427 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -159,8 +159,8 @@ type tdInt int type f struct { BaseNode - Parents []*f `gogm:"direction=outgoing;relationship=test"` - Children []*f `gogm:"direction=incoming;relationship=test"` + Parents []*f `gogm:"direction=outgoing;relationship=test"` + Children []*f `gogm:"direction=incoming;relationship=test"` } type a struct { @@ -545,7 +545,7 @@ func TestDecoder(t *testing.T) { Id: 2, UUID: "dasdfas", }, - TestTime: fTime, + TestTime: fTime, }, }, } @@ -613,7 +613,7 @@ func TestDecoder(t *testing.T) { Id: 2, UUID: "dasdfas", }, - TestTime: fTime, + TestTime: fTime, } c4 := c{ diff --git a/decorator.go b/decorator.go index 2f63c44..717ab55 100644 --- a/decorator.go +++ b/decorator.go @@ -471,7 +471,7 @@ func getFields(val reflect.Type) []*reflect.StructField { for i := 0; i < val.NumField(); i++ { tempField := val.Field(i) - if tempField.Anonymous && tempField.Type.Kind() == reflect.Struct{ + if tempField.Anonymous && tempField.Type.Kind() == reflect.Struct { fields = append(fields, getFields(tempField.Type)...) } else { fields = append(fields, &tempField) @@ -479,4 +479,4 @@ func getFields(val reflect.Type) []*reflect.StructField { } return fields -} \ No newline at end of file +} diff --git a/decorator_test.go b/decorator_test.go index e278c91..7f1b46f 100644 --- a/decorator_test.go +++ b/decorator_test.go @@ -307,8 +307,8 @@ func TestNewDecoratorConfig(t *testing.T) { //structs with decorators for testing type embedTest struct { - Id int64 `gogm:"name=id"` - UUID string `gogm:"pk;name=uuid"` + Id int64 `gogm:"name=id"` + UUID string `gogm:"pk;name=uuid"` } type validStruct struct { diff --git a/save.go b/save.go index e0e3412..c197447 100644 --- a/save.go +++ b/save.go @@ -27,7 +27,7 @@ type relCreateConf struct { type relDelConf struct { StartNodeId string - EndNodeId int64 + EndNodeId int64 } //todo optimize @@ -148,7 +148,7 @@ func saveDepth(sess *driver.BoltConn, obj interface{}, depth int) error { wg.Wait() - if err1 != nil || err2 != nil || err3 != nil{ + if err1 != nil || err2 != nil || err3 != nil { return fmt.Errorf("delErr=(%v) | relErr=(%v) | reallocErr=(%v)", err1, err2, err3) } else { return nil @@ -179,7 +179,7 @@ func calculateDels(oldRels, curRels map[string]map[string]*RelationConfig) map[s } for _, id := range oldConf.Ids { //check if this id is new rels in the same location - if deleteAllRels || deleteAllRelsOnField{ + if deleteAllRels || deleteAllRelsOnField { if _, ok := dels[uuid]; !ok { dels[uuid] = []int64{id} } else { @@ -216,7 +216,7 @@ func removeRelations(conn *driver.BoltConn, dels map[string][]int64) error { for uuid, ids := range dels { params = append(params, map[string]interface{}{ "startNodeId": uuid, - "endNodeIds": ids, + "endNodeIds": ids, }) } @@ -231,20 +231,20 @@ func removeRelations(conn *driver.BoltConn, dels map[string][]int64) error { Cypher("UNWIND {rows} as row"). Match(dsl.Path(). V(dsl.V{ - Name: "start", + Name: "start", Params: startParams, }).E(dsl.E{ - Name: "e", - }).V(dsl.V{ - Name: "end", - }).Build()). + Name: "e", + }).V(dsl.V{ + Name: "end", + }).Build()). Cypher("WHERE id(end) IN row.endNodeIds"). Delete(false, "e"). WithNeo(conn). Exec(map[string]interface{}{ "rows": params, }, - ) + ) if err != nil { return fmt.Errorf("%s, %w", err.Error(), ErrInternal) } @@ -468,7 +468,7 @@ func parseValidate(currentDepth, maxDepth int, current *reflect.Value, nodesPtr return nil } -func generateCurRels(parentId string, current *reflect.Value, currentDepth, maxDepth int, curRels *map[string]map[string]*RelationConfig) error { +func generateCurRels(parentId string, current *reflect.Value, currentDepth, maxDepth int, curRels *map[string]map[string]*RelationConfig) error { if currentDepth > maxDepth { return nil } @@ -541,7 +541,7 @@ func generateCurRels(parentId string, current *reflect.Value, currentDepth, maxD //check the config is there for the specified field if _, ok = (*curRels)[uuid][conf.FieldName]; !ok { (*curRels)[uuid][conf.FieldName] = &RelationConfig{ - Ids: []int64{}, + Ids: []int64{}, RelationType: Multi, } } @@ -572,7 +572,7 @@ func generateCurRels(parentId string, current *reflect.Value, currentDepth, maxD //check the config is there for the specified field if _, ok = (*curRels)[uuid][conf.FieldName]; !ok { (*curRels)[uuid][conf.FieldName] = &RelationConfig{ - Ids: []int64{}, + Ids: []int64{}, RelationType: Single, } } @@ -631,7 +631,7 @@ func parseStruct(parentId, edgeLabel string, parentIsStart bool, direction dsl.D } if !isNewNode { - if _, ok := (*oldRels)[id]; !ok{ + if _, ok := (*oldRels)[id]; !ok { iConf := reflect.Indirect(*current).FieldByName("LoadMap").Interface() var relConf map[string]*RelationConfig @@ -735,7 +735,7 @@ func parseStruct(parentId, edgeLabel string, parentIsStart bool, direction dsl.D if skip { continue } - + err = parseStruct(newParentId, newEdgeLabel, newParentIdStart, newDirection, newEdgeParams, followVal, currentDepth+1, maxDepth, nodesPtr, relationsPtr, oldRels, newNodes, nodeRef) if err != nil { return err diff --git a/save_test.go b/save_test.go index ce48709..306ea83 100644 --- a/save_test.go +++ b/save_test.go @@ -27,11 +27,11 @@ func parseO2O(req *require.Assertions) { TestTypeDefString: "dasdfas", TestTypeDefInt: 600, BaseNode: BaseNode{ - Id: 1, - UUID: "comp1uuid", + Id: 1, + UUID: "comp1uuid", LoadMap: map[string]*RelationConfig{ "SingleSpecA": { - Ids: []int64{2}, + Ids: []int64{2}, RelationType: Single, }, }, @@ -40,11 +40,11 @@ func parseO2O(req *require.Assertions) { b1 := &b{ BaseNode: BaseNode{ - Id: 2, - UUID: "b1uuid", + Id: 2, + UUID: "b1uuid", LoadMap: map[string]*RelationConfig{ "SingleSpec": { - Ids: []int64{1}, + Ids: []int64{1}, RelationType: Single, }, }, @@ -91,26 +91,26 @@ func parseM2O(req *require.Assertions) { TestTypeDefString: "dasdfas", TestTypeDefInt: 600, BaseNode: BaseNode{ - Id: 1, - UUID: "a1uuid", + Id: 1, + UUID: "a1uuid", LoadMap: map[string]*RelationConfig{ "ManyA": { - Ids: []int64{2}, + Ids: []int64{2}, RelationType: Multi, }, }, }, - ManyA: []*b{}, + ManyA: []*b{}, } b1 := &b{ TestField: "test", BaseNode: BaseNode{ - Id: 2, - UUID: "b1uuid", + Id: 2, + UUID: "b1uuid", LoadMap: map[string]*RelationConfig{ - "ManyB" : { - Ids: []int64{1}, + "ManyB": { + Ids: []int64{1}, RelationType: Single, }, }, @@ -144,32 +144,32 @@ func parseM2M(req *require.Assertions) { TestTypeDefString: "dasdfas", TestTypeDefInt: 600, BaseNode: BaseNode{ - Id: 1, - UUID: "a1uuid", + Id: 1, + UUID: "a1uuid", LoadMap: map[string]*RelationConfig{ "MultiA": { - Ids: []int64{2}, + Ids: []int64{2}, RelationType: Multi, }, }, }, - ManyA: []*b{}, - MultiA: []*b{}, + ManyA: []*b{}, + MultiA: []*b{}, } b1 := &b{ TestField: "test", BaseNode: BaseNode{ - Id: 2, - UUID: "b1uuid", + Id: 2, + UUID: "b1uuid", LoadMap: map[string]*RelationConfig{ "Multi": { - Ids: []int64{1}, + Ids: []int64{1}, RelationType: Multi, }, }, }, - Multi: []*a{}, + Multi: []*a{}, } b1.Multi = append(b1.Multi, a1) @@ -201,20 +201,20 @@ func TestCalculateDels(t *testing.T) { dels := calculateDels(map[string]map[string]*RelationConfig{ "node1": { "RelField": { - Ids: []int64{2}, + Ids: []int64{2}, RelationType: Single, }, }, "node2": { "RelField2": { - Ids: []int64{1}, + Ids: []int64{1}, RelationType: Single, }, }, }, map[string]map[string]*RelationConfig{ "node1": { "RelField": { - Ids: []int64{}, + Ids: []int64{}, RelationType: Single, }, }, @@ -228,26 +228,26 @@ func TestCalculateDels(t *testing.T) { dels = calculateDels(map[string]map[string]*RelationConfig{ "node1": { "RelField": { - Ids: []int64{2}, + Ids: []int64{2}, RelationType: Single, }, }, "node2": { "RelField2": { - Ids: []int64{1}, + Ids: []int64{1}, RelationType: Single, }, }, }, map[string]map[string]*RelationConfig{ "node1": { "RelField": { - Ids: []int64{}, + Ids: []int64{}, RelationType: Single, }, }, "node2": { "RelFieldNew": { - Ids: []int64{}, + Ids: []int64{}, RelationType: Single, }, }, @@ -262,26 +262,26 @@ func TestCalculateDels(t *testing.T) { dels = calculateDels(map[string]map[string]*RelationConfig{ "node1": { "RelField": { - Ids: []int64{2}, + Ids: []int64{2}, RelationType: Single, }, }, "node2": { "RelField2": { - Ids: []int64{1}, + Ids: []int64{1}, RelationType: Single, }, }, }, map[string]map[string]*RelationConfig{ "node1": { "RelField": { - Ids: []int64{}, + Ids: []int64{}, RelationType: Single, }, }, "node2": { "RelField2": { - Ids: []int64{}, + Ids: []int64{}, RelationType: Single, }, }, @@ -296,26 +296,26 @@ func TestCalculateDels(t *testing.T) { dels = calculateDels(map[string]map[string]*RelationConfig{ "node1": { "RelField": { - Ids: []int64{2}, + Ids: []int64{2}, RelationType: Single, }, }, "node2": { "RelField2": { - Ids: []int64{1}, + Ids: []int64{1}, RelationType: Single, }, }, }, map[string]map[string]*RelationConfig{ "node1": { "RelField": { - Ids: []int64{2}, + Ids: []int64{2}, RelationType: Single, }, }, "node2": { "RelField2": { - Ids: []int64{1}, + Ids: []int64{1}, RelationType: Single, }, }, @@ -372,23 +372,23 @@ func TestSave(t *testing.T) { req.Nil(saveDepth(conn, a2, 5)) req.EqualValues(map[string]*RelationConfig{ "SingleSpecA": { - Ids: []int64{b2.Id}, + Ids: []int64{b2.Id}, RelationType: Single, }, "ManyA": { - Ids: []int64{b3.Id}, + Ids: []int64{b3.Id}, RelationType: Multi, }, }, a2.LoadMap) req.EqualValues(map[string]*RelationConfig{ "SingleSpec": { - Ids: []int64{a2.Id}, + Ids: []int64{a2.Id}, RelationType: Single, }, }, b2.LoadMap) req.EqualValues(map[string]*RelationConfig{ "ManyB": { - Ids: []int64{a2.Id}, + Ids: []int64{a2.Id}, RelationType: Single, }, }, b3.LoadMap) diff --git a/setup.go b/setup.go index ce4c605..0f58ca6 100644 --- a/setup.go +++ b/setup.go @@ -88,7 +88,7 @@ func Init(conf *Config, mapTypes ...interface{}) error { func setupInit(isTest bool, conf *Config, mapTypes ...interface{}) error { if isSetup && !isTest { return errors.New("gogm has already been initialized") - } else if isTest && isSetup{ + } else if isTest && isSetup { mappedRelations = &relationConfigs{} } diff --git a/testing_/linking.go b/testing_/linking.go index 16a1a6b..85fb24d 100644 --- a/testing_/linking.go +++ b/testing_/linking.go @@ -4,9 +4,8 @@ package testing_ import ( "errors" ) - -func(l *ExampleObject) LinkToExampleObject2OnFieldSpecial(target *ExampleObject2, edge *SpecialEdge) error { +func (l *ExampleObject) LinkToExampleObject2OnFieldSpecial(target *ExampleObject2, edge *SpecialEdge) error { if target == nil { return errors.New("start and end can not be nil") } @@ -14,19 +13,19 @@ func(l *ExampleObject) LinkToExampleObject2OnFieldSpecial(target *ExampleObject2 if edge == nil { return errors.New("edge can not be nil") } - + err := edge.SetStartNode(l) if err != nil { return err } - + err = edge.SetEndNode(target) if err != nil { return err } - + l.Special = edge - + if target.Special == nil { target.Special = make([]*SpecialEdge, 1, 1) target.Special[0] = edge @@ -37,16 +36,16 @@ func(l *ExampleObject) LinkToExampleObject2OnFieldSpecial(target *ExampleObject2 return nil } -func(l *ExampleObject) UnlinkFromExampleObject2OnFieldSpecial(target *ExampleObject2) error { +func (l *ExampleObject) UnlinkFromExampleObject2OnFieldSpecial(target *ExampleObject2) error { if target == nil { return errors.New("start and end can not be nil") } - + l.Special = nil - + if target.Special != nil { for i, unlinkTarget := range target.Special { - + obj := unlinkTarget.GetStartNode() checkObj, ok := obj.(*ExampleObject) @@ -64,35 +63,35 @@ func(l *ExampleObject) UnlinkFromExampleObject2OnFieldSpecial(target *ExampleObj } return nil -} +} -func(l *ExampleObject) LinkToExampleObjectOnFieldChildren(targets ...*ExampleObject) error { +func (l *ExampleObject) LinkToExampleObjectOnFieldChildren(targets ...*ExampleObject) error { if targets == nil { return errors.New("start and end can not be nil") } for _, target := range targets { - + if l.Children == nil { l.Children = make([]*ExampleObject, 1, 1) l.Children[0] = target } else { l.Children = append(l.Children, target) } - + target.Parents = l } return nil } -func(l *ExampleObject) UnlinkFromExampleObjectOnFieldChildren(targets ...*ExampleObject) error { +func (l *ExampleObject) UnlinkFromExampleObjectOnFieldChildren(targets ...*ExampleObject) error { if targets == nil { return errors.New("start and end can not be nil") } for _, target := range targets { - + if l.Children != nil { for i, unlinkTarget := range l.Children { if unlinkTarget.UUID == target.UUID { @@ -104,19 +103,19 @@ func(l *ExampleObject) UnlinkFromExampleObjectOnFieldChildren(targets ...*Exampl } } } - + target.Parents = nil } return nil -} -func(l *ExampleObject) LinkToExampleObjectOnFieldParents(target *ExampleObject) error { +} +func (l *ExampleObject) LinkToExampleObjectOnFieldParents(target *ExampleObject) error { if target == nil { return errors.New("start and end can not be nil") } - + l.Parents = target - + if target.Children == nil { target.Children = make([]*ExampleObject, 1, 1) target.Children[0] = l @@ -127,13 +126,13 @@ func(l *ExampleObject) LinkToExampleObjectOnFieldParents(target *ExampleObject) return nil } -func(l *ExampleObject) UnlinkFromExampleObjectOnFieldParents(target *ExampleObject) error { +func (l *ExampleObject) UnlinkFromExampleObjectOnFieldParents(target *ExampleObject) error { if target == nil { return errors.New("start and end can not be nil") } - + l.Parents = nil - + if target.Children != nil { for i, unlinkTarget := range target.Children { if unlinkTarget.UUID == l.UUID { @@ -147,9 +146,9 @@ func(l *ExampleObject) UnlinkFromExampleObjectOnFieldParents(target *ExampleObje } return nil -} +} -func(l *ExampleObject2) LinkToExampleObjectOnFieldSpecial(target *ExampleObject, edge *SpecialEdge) error { +func (l *ExampleObject2) LinkToExampleObjectOnFieldSpecial(target *ExampleObject, edge *SpecialEdge) error { if target == nil { return errors.New("start and end can not be nil") } @@ -157,37 +156,37 @@ func(l *ExampleObject2) LinkToExampleObjectOnFieldSpecial(target *ExampleObject, if edge == nil { return errors.New("edge can not be nil") } - + err := edge.SetStartNode(target) if err != nil { return err } - + err = edge.SetEndNode(l) if err != nil { return err } - + if l.Special == nil { l.Special = make([]*SpecialEdge, 1, 1) l.Special[0] = edge } else { l.Special = append(l.Special, edge) } - + target.Special = edge return nil } -func(l *ExampleObject2) UnlinkFromExampleObjectOnFieldSpecial(target *ExampleObject) error { +func (l *ExampleObject2) UnlinkFromExampleObjectOnFieldSpecial(target *ExampleObject) error { if target == nil { return errors.New("start and end can not be nil") } - + if l.Special != nil { for i, unlinkTarget := range l.Special { - + obj := unlinkTarget.GetEndNode() checkObj, ok := obj.(*ExampleObject) @@ -203,39 +202,39 @@ func(l *ExampleObject2) UnlinkFromExampleObjectOnFieldSpecial(target *ExampleObj } } } - + target.Special = nil return nil -} +} -func(l *ExampleObject2) LinkToExampleObject2OnFieldChildren2(targets ...*ExampleObject2) error { +func (l *ExampleObject2) LinkToExampleObject2OnFieldChildren2(targets ...*ExampleObject2) error { if targets == nil { return errors.New("start and end can not be nil") } for _, target := range targets { - + if l.Children2 == nil { l.Children2 = make([]*ExampleObject2, 1, 1) l.Children2[0] = target } else { l.Children2 = append(l.Children2, target) } - + target.Parents2 = l } return nil } -func(l *ExampleObject2) UnlinkFromExampleObject2OnFieldChildren2(targets ...*ExampleObject2) error { +func (l *ExampleObject2) UnlinkFromExampleObject2OnFieldChildren2(targets ...*ExampleObject2) error { if targets == nil { return errors.New("start and end can not be nil") } for _, target := range targets { - + if l.Children2 != nil { for i, unlinkTarget := range l.Children2 { if unlinkTarget.UUID == target.UUID { @@ -247,19 +246,19 @@ func(l *ExampleObject2) UnlinkFromExampleObject2OnFieldChildren2(targets ...*Exa } } } - + target.Parents2 = nil } return nil -} -func(l *ExampleObject2) LinkToExampleObject2OnFieldParents2(target *ExampleObject2) error { +} +func (l *ExampleObject2) LinkToExampleObject2OnFieldParents2(target *ExampleObject2) error { if target == nil { return errors.New("start and end can not be nil") } - + l.Parents2 = target - + if target.Children2 == nil { target.Children2 = make([]*ExampleObject2, 1, 1) target.Children2[0] = l @@ -270,13 +269,13 @@ func(l *ExampleObject2) LinkToExampleObject2OnFieldParents2(target *ExampleObjec return nil } -func(l *ExampleObject2) UnlinkFromExampleObject2OnFieldParents2(target *ExampleObject2) error { +func (l *ExampleObject2) UnlinkFromExampleObject2OnFieldParents2(target *ExampleObject2) error { if target == nil { return errors.New("start and end can not be nil") } - + l.Parents2 = nil - + if target.Children2 != nil { for i, unlinkTarget := range target.Children2 { if unlinkTarget.UUID == l.UUID { @@ -290,4 +289,4 @@ func(l *ExampleObject2) UnlinkFromExampleObject2OnFieldParents2(target *ExampleO } return nil -} \ No newline at end of file +} diff --git a/testing_/linking_test.go b/testing_/linking_test.go index ada6815..2afe773 100644 --- a/testing_/linking_test.go +++ b/testing_/linking_test.go @@ -43,7 +43,7 @@ func TestLinking(t *testing.T) { } obj3 := &ExampleObject2{ - BaseNode: gogm.BaseNode{ + BaseNode: gogm.BaseNode{ UUID: "adfadsfasd", }, } diff --git a/testing_/test_edge.go b/testing_/test_edge.go index bd26dc3..8c13649 100644 --- a/testing_/test_edge.go +++ b/testing_/test_edge.go @@ -9,7 +9,7 @@ type SpecialEdge struct { gogm.BaseNode Start *ExampleObject - End *ExampleObject2 + End *ExampleObject2 SomeField string `gogm:"name=some_field"` } @@ -39,5 +39,3 @@ func (s *SpecialEdge) SetEndNode(v interface{}) error { s.End = v.(*ExampleObject2) return nil } - - diff --git a/testing_/test_obj.go b/testing_/test_obj.go index 3a2f477..6335e41 100644 --- a/testing_/test_obj.go +++ b/testing_/test_obj.go @@ -6,6 +6,6 @@ type ExampleObject struct { gogm.BaseNode Children []*ExampleObject `gogm:"direction=incoming;relationship=test" json:"children"` - Parents *ExampleObject `gogm:"direction=outgoing;relationship=test" json:"parents"` - Special *SpecialEdge `gogm:"direction=incoming;relationship=special" json:"special"` + Parents *ExampleObject `gogm:"direction=outgoing;relationship=test" json:"parents"` + Special *SpecialEdge `gogm:"direction=incoming;relationship=special" json:"special"` } diff --git a/testing_/test_obj2.go b/testing_/test_obj2.go index 4d88de5..2d6df36 100644 --- a/testing_/test_obj2.go +++ b/testing_/test_obj2.go @@ -6,6 +6,6 @@ type ExampleObject2 struct { gogm.BaseNode Children2 []*ExampleObject2 `gogm:"direction=incoming;relationship=test" json:"children_2"` - Parents2 *ExampleObject2 `gogm:"direction=outgoing;relationship=test" json:"parents_2"` - Special []*SpecialEdge `gogm:"direction=outgoing;relationship=special" json:"special"` + Parents2 *ExampleObject2 `gogm:"direction=outgoing;relationship=test" json:"parents_2"` + Special []*SpecialEdge `gogm:"direction=outgoing;relationship=special" json:"special"` } diff --git a/util.go b/util.go index 660af09..e062412 100644 --- a/util.go +++ b/util.go @@ -219,8 +219,8 @@ func (r *relationConfigs) getConfig(nodeType, relationship, fieldType string, di type validation struct { Incoming []string Outgoing []string - None []string - Both []string + None []string + Both []string } func (r *relationConfigs) Validate() error { @@ -273,20 +273,20 @@ func (r *relationConfigs) Validate() error { for relType, validateConfig := range checkMap { //check normal - if len(validateConfig.Outgoing) != len(validateConfig.Incoming){ + if len(validateConfig.Outgoing) != len(validateConfig.Incoming) { return fmt.Errorf("invalid directional configuration on relationship [%s], %w", relType, ErrValidation) } //check both direction if len(validateConfig.Both) != 0 { - if len(validateConfig.Both) % 2 != 0 { + if len(validateConfig.Both)%2 != 0 { return fmt.Errorf("invalid length for 'both' validation, %w", ErrValidation) } } //check none direction if len(validateConfig.None) != 0 { - if len(validateConfig.None) % 2 != 0 { + if len(validateConfig.None)%2 != 0 { return fmt.Errorf("invalid length for 'both' validation, %w", ErrValidation) } } From 07969f481740f6247df7bcbe3dabb7ad963ad925 Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Tue, 3 Dec 2019 13:07:58 -0500 Subject: [PATCH 11/27] comment out integration test --- save_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/save_test.go b/save_test.go index 306ea83..0bed0b0 100644 --- a/save_test.go +++ b/save_test.go @@ -325,7 +325,7 @@ func TestCalculateDels(t *testing.T) { } func TestSave(t *testing.T) { - //t.Skip() + t.Skip() req := require.New(t) conf := Config{ From 5ce1a53d2167f0803eb4a60428ad2b7c3a1acce2 Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Wed, 4 Dec 2019 14:33:21 -0500 Subject: [PATCH 12/27] ready --- setup.go => config.go | 0 setup_test.go => config_test.go | 0 decorator.go | 79 +++++++++++++++++++++++---------- save.go | 5 --- 4 files changed, 56 insertions(+), 28 deletions(-) rename setup.go => config.go (100%) rename setup_test.go => config_test.go (100%) diff --git a/setup.go b/config.go similarity index 100% rename from setup.go rename to config.go diff --git a/setup_test.go b/config_test.go similarity index 100% rename from setup_test.go rename to config_test.go diff --git a/decorator.go b/decorator.go index 717ab55..7966d7e 100644 --- a/decorator.go +++ b/decorator.go @@ -29,21 +29,62 @@ const ( ) type decoratorConfig struct { - Type reflect.Type - Name string - FieldName string - Relationship string - Direction dsl.Direction - Unique bool - Index bool - ManyRelationship bool - UsesEdgeNode bool - PrimaryKey bool - Properties bool - IsTime bool - IsTypeDef bool - TypedefActual reflect.Type - Ignore bool + Type reflect.Type `json:"-"` + Name string `json:"name"` + FieldName string `json:"field_name"` + Relationship string `json:"relationship"` + Direction dsl.Direction `json:"direction"` + Unique bool `json:"unique"` + Index bool `json:"index"` + ManyRelationship bool `json:"many_relationship"` + UsesEdgeNode bool `json:"uses_edge_node"` + PrimaryKey bool `json:"primary_key"` + Properties bool `json:"properties"` + IsTime bool `json:"is_time"` + IsTypeDef bool `json:"is_type_def"` + TypedefActual reflect.Type `json:"-"` + Ignore bool `json:"ignore"` +} + +func (d *decoratorConfig) Equals(comp *decoratorConfig) bool { + if comp == nil { + return false + } + + return d.Name == comp.Name && d.FieldName == comp.FieldName && d.Relationship == comp.Relationship && + d.Direction == comp.Direction && d.Unique == comp.Unique && d.Index == comp.Index && d.ManyRelationship == comp.ManyRelationship && + d.UsesEdgeNode == comp.UsesEdgeNode && d.PrimaryKey == comp.PrimaryKey && d.Properties == comp.Properties && d.IsTime == comp.IsTime && + d.IsTypeDef == comp.IsTypeDef && d.Ignore == comp.Ignore +} + +type structDecoratorConfig struct { + // field name : decorator configuration + Fields map[string]decoratorConfig `json:"fields"` + Label string `json:"label"` + IsVertex bool `json:"is_vertex"` + Type reflect.Type `json:"-"` +} + +func (s *structDecoratorConfig) Equals(comp *structDecoratorConfig) bool { + if comp == nil { + return false + } + + if comp.Fields != nil && s.Fields != nil { + for field, decConfig := range s.Fields { + if compConfig, ok := comp.Fields[field]; ok { + if !compConfig.Equals(&decConfig) { + return false + } + } else { + return false + } + } + } else { + return false + } + + return s.IsVertex == comp.IsVertex && s.Label == comp.Label } //have struct validate itself @@ -298,14 +339,6 @@ func newDecoratorConfig(decorator, name string, varType reflect.Type) (*decorato return &toReturn, nil } -type structDecoratorConfig struct { - // field name : decorator configuration - Fields map[string]decoratorConfig - Label string - IsVertex bool - Type reflect.Type -} - //validates struct configuration func (s *structDecoratorConfig) Validate() error { if s.Fields == nil { diff --git a/save.go b/save.go index c197447..e36b068 100644 --- a/save.go +++ b/save.go @@ -25,11 +25,6 @@ type relCreateConf struct { Direction dsl.Direction } -type relDelConf struct { - StartNodeId string - EndNodeId int64 -} - //todo optimize func saveDepth(sess *driver.BoltConn, obj interface{}, depth int) error { if sess == nil { From b58e5d6248a0d378dc36ac1bc15e08b44673c7d6 Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Wed, 4 Dec 2019 16:41:44 -0500 Subject: [PATCH 13/27] integration testing set up --- .github/workflows/docker-compose.yaml | 10 +++ .github/workflows/go.yml | 27 +++++-- config.go | 6 ++ decoder_test.go | 46 ------------ delete_test.go | 12 +-- index_test.go | 63 +--------------- integration_test.go | 101 ++++++++++++++++++++++++++ save_test.go | 88 ++-------------------- session_test.go | 37 ---------- 9 files changed, 150 insertions(+), 240 deletions(-) create mode 100644 .github/workflows/docker-compose.yaml create mode 100644 integration_test.go delete mode 100644 session_test.go diff --git a/.github/workflows/docker-compose.yaml b/.github/workflows/docker-compose.yaml new file mode 100644 index 0000000..f43e866 --- /dev/null +++ b/.github/workflows/docker-compose.yaml @@ -0,0 +1,10 @@ +version: "3.6" +services: + neo: + image: neo4j:enterprise + environment: + - NEO4J_ACCEPT_LICENSE_AGREEMENT=yes + - NEO4J_AUTH=neo4j/password + ports: + - "7474:7474" + - "7687:7687" \ No newline at end of file diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 18bb488..4410d3b 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -6,16 +6,13 @@ jobs: name: Build runs-on: ubuntu-latest steps: - - name: Set up Go 1.13 uses: actions/setup-go@v1 with: go-version: 1.13 id: go - - name: Check out code into the Go module directory uses: actions/checkout@v1 - - name: Get dependencies run: | go get -v -t -d ./... @@ -23,8 +20,24 @@ jobs: curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh dep ensure fi - - name: Test - run: go test ./... - - name: Build - run: go build -v . + run: go build -v . + - name: Run Unit Tests + run: go test ./... -short + - name: Start Neo4j Docker + run: | + docker-compose up -d + - name: Wait for neo4j to be ready + run: | + end="$((SECONDS+60))" + while true; do + nc -w 2 localhost 7687 && break + [[ "${SECONDS}" -ge "${end}" ]] && exit 1 + sleep 1 + done + - name: Run Integration Test + run: go test ./... -run Integration + - name: Stop Neo4j Docker + run: | + docker-compose down + diff --git a/config.go b/config.go index 0f58ca6..a9a44f6 100644 --- a/config.go +++ b/config.go @@ -85,6 +85,12 @@ func Init(conf *Config, mapTypes ...interface{}) error { return setupInit(false, conf, mapTypes...) } +func Reset() { + mappedTypes = &hashmap.HashMap{} + mappedRelations = &relationConfigs{} + isSetup = false +} + func setupInit(isTest bool, conf *Config, mapTypes ...interface{}) error { if isSetup && !isTest { return errors.New("gogm has already been initialized") diff --git a/decoder_test.go b/decoder_test.go index 7c94427..4a006d6 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -3,8 +3,6 @@ package gogm import ( "errors" "github.com/cornelk/hashmap" - dsl "github.com/mindstand/go-cypherdsl" - driver "github.com/mindstand/golang-neo4j-bolt-driver" "github.com/mindstand/golang-neo4j-bolt-driver/structures/graph" "github.com/stretchr/testify/require" "reflect" @@ -12,50 +10,6 @@ import ( "time" ) -func TestDecode(t *testing.T) { - if !testing.Short() { - t.Skip() - return - } - - req := require.New(t) - - req.Nil(setupInit(false, &Config{ - Host: "0.0.0.0", - Port: 7687, - IsCluster: false, - Username: "neo4j", - Password: "password", - PoolSize: 50, - IndexStrategy: IGNORE_INDEX, - }, &a{}, &b{}, &c{})) - - req.EqualValues(3, mappedTypes.Len()) - - query := `match p=(n:a{uuid:'d5c56567-da8e-429f-9cea-300e722195e0'})-[*0..4]-() return p` - - conn, err := driverPool.Open(driver.ReadWriteMode) - if err != nil { - require.Nil(t, err) - } - defer driverPool.Reclaim(conn) - - rows, err := dsl.QB().WithNeo(conn).Cypher(query).Query(nil) - require.Nil(t, err) - require.NotNil(t, rows) - - var stuff a - require.Nil(t, decodeNeoRows(rows, &stuff)) - t.Log(stuff.Id) - t.Log(stuff.UUID) - t.Log(stuff) - req.NotNil(stuff.SingleSpecA) - req.NotNil(stuff.SingleSpecA.End.Single) - //t.Log(stuff.MultiSpecA[0].End.Id) - //req.NotEqual(0, stuff.Id) - //req.True(len(stuff.MultiSpecA) > 0) -} - type TestStruct struct { Id int64 UUID string diff --git a/delete_test.go b/delete_test.go index 140a3bf..6e3f484 100644 --- a/delete_test.go +++ b/delete_test.go @@ -3,17 +3,13 @@ package gogm import ( driver "github.com/mindstand/golang-neo4j-bolt-driver" "github.com/stretchr/testify/require" - "testing" + ) -func TestDelete(t *testing.T) { - if !testing.Short() { - t.Skip() - return - } +func testDelete(req *require.Assertions) { conn, err := driverPool.Open(driver.ReadWriteMode) if err != nil { - require.Nil(t, err) + req.Nil(err) } defer driverPool.Reclaim(conn) @@ -25,5 +21,5 @@ func TestDelete(t *testing.T) { } err = deleteNode(conn, &del) - require.Nil(t, err) + req.Nil(err) } diff --git a/index_test.go b/index_test.go index 72ef983..12b5c2a 100644 --- a/index_test.go +++ b/index_test.go @@ -1,72 +1,15 @@ package gogm import ( - dsl "github.com/mindstand/go-cypherdsl" driver "github.com/mindstand/golang-neo4j-bolt-driver" "github.com/stretchr/testify/require" "reflect" - "testing" ) -func TestDropAllIndexesAndConstraints(t *testing.T) { - //requires connection - if !testing.Short() { - t.SkipNow() - return - } - - conn, err := driverPool.Open(driver.ReadWriteMode) - if err != nil { - require.Nil(t, err) - } - defer driverPool.Reclaim(conn) - require.Nil(t, err) - - err = dropAllIndexesAndConstraints() - require.Nil(t, err) - - constraintRows, err := dsl.QB().WithNeo(conn).Cypher("CALL db.constraints").Query(nil) - require.Nil(t, err) - - found, _, err := constraintRows.All() - require.Nil(t, err) - - require.Equal(t, 0, len(found)) - - indexRows, err := dsl.QB().WithNeo(conn).Cypher("CALL db.indexes()").Query(nil) - require.Nil(t, err) - - iFound, _, err := indexRows.All() - require.Nil(t, err) - - require.Equal(t, 0, len(iFound)) -} - -func TestIndexManagement(t *testing.T) { - //requires connection - if !testing.Short() { - t.SkipNow() - return - } - - req := require.New(t) - - var err error - - conf := Config{ - Username: "neo4j", - Password: "password", - Host: "0.0.0.0", - Port: 7687, - PoolSize: 15, - } - - driverPool, err = driver.NewClosableDriverPool(conf.ConnectionString(), conf.PoolSize) - req.Nil(err) - +func testIndexManagement(req *require.Assertions) { //init conn, err := driverPool.Open(driver.ReadWriteMode) - require.Nil(t, err) + req.Nil(err) defer driverPool.Reclaim(conn) req.Nil(err) @@ -123,7 +66,7 @@ func TestIndexManagement(t *testing.T) { //create stuff req.Nil(createAllIndexesAndConstraints(mapp)) - t.Log("created indices and constraints") + log.Println("created indices and constraints") //validate req.Nil(verifyAllIndexesAndConstraints(mapp)) diff --git a/integration_test.go b/integration_test.go new file mode 100644 index 0000000..1a82830 --- /dev/null +++ b/integration_test.go @@ -0,0 +1,101 @@ +package gogm + +import ( + "github.com/stretchr/testify/require" + "testing" + "time" +) + +func TestIntegration(t *testing.T) { + if testing.Short() { + t.Skip() + } + + req := require.New(t) + + conf := Config{ + Username: "neo4j", + Password: "password", + Host: "0.0.0.0", + Port: 7687, + PoolSize: 15, + IndexStrategy: IGNORE_INDEX, + } + + req.Nil(Init(&conf, &a{}, &b{}, &c{})) + + sess, err := NewSession(false) + req.Nil(err) + defer sess.Close() + + log.Println("testIndexManagement") + testIndexManagement(req) + + log.Println("test save") + testSave(sess, req) + + req.Nil(sess.PurgeDatabase()) +} + +// runs with integration test +func testSave(sess *Session, req *require.Assertions) { + req.Nil(sess.Begin()) + a2 := &a{ + TestField: "test", + } + + b2 := &b{ + TestField: "test", + TestTime: time.Now().UTC(), + } + + b3 := &b{ + TestField: "dasdfasd", + } + + c1 := &c{ + Start: a2, + End: b2, + Test: "testing", + } + + a2.SingleSpecA = c1 + a2.ManyA = []*b{b3} + b2.SingleSpec = c1 + b3.ManyB = a2 + + req.Nil(sess.SaveDepth(a2, 5)) + + req.Nil(sess.Commit()) + req.Nil(sess.Begin()) + + req.EqualValues(map[string]*RelationConfig{ + "SingleSpecA": { + Ids: []int64{b2.Id}, + RelationType: Single, + }, + "ManyA": { + Ids: []int64{b3.Id}, + RelationType: Multi, + }, + }, a2.LoadMap) + req.EqualValues(map[string]*RelationConfig{ + "SingleSpec": { + Ids: []int64{a2.Id}, + RelationType: Single, + }, + }, b2.LoadMap) + req.EqualValues(map[string]*RelationConfig{ + "ManyB": { + Ids: []int64{a2.Id}, + RelationType: Single, + }, + }, b3.LoadMap) + a2.SingleSpecA = nil + b2.SingleSpec = nil + + req.Nil(sess.SaveDepth(a2, 5)) + req.Nil(sess.Commit()) + req.Nil(a2.SingleSpecA) + req.Nil(b2.SingleSpec) +} diff --git a/save_test.go b/save_test.go index 0bed0b0..b63ca88 100644 --- a/save_test.go +++ b/save_test.go @@ -1,11 +1,9 @@ package gogm import ( - driver "github.com/mindstand/golang-neo4j-bolt-driver" "github.com/stretchr/testify/require" "reflect" "testing" - "time" ) func TestParseStruct(t *testing.T) { @@ -70,13 +68,12 @@ func parseO2O(req *require.Assertions) { val := reflect.ValueOf(comp1) nodeRef := map[string]*reflect.Value{} - err := parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations, &oldRels, &ids, &nodeRef) - req.Nil(err) + req.Nil(parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations, &oldRels, &ids, &nodeRef)) + req.Nil(generateCurRels("", &val, 0, 5, &curRels)) req.Equal(2, len(nodes)) req.Equal(1, len(nodes["a"])) req.Equal(1, len(nodes["b"])) req.Equal(1, len(relations)) - req.Equal(2, len(oldRels)) req.Equal(2, len(curRels)) req.Equal(int64(2), curRels["comp1uuid"]["SingleSpecA"].Ids[0]) @@ -128,8 +125,8 @@ func parseM2O(req *require.Assertions) { val := reflect.ValueOf(a1) nodeRef := map[string]*reflect.Value{} - err := parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations, &oldRels, &ids, &nodeRef) - req.Nil(err) + req.Nil(parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations, &oldRels, &ids, &nodeRef)) + req.Nil(generateCurRels("", &val, 0, 5, &curRels)) req.Equal(2, len(nodes)) req.Equal(1, len(nodes["a"])) req.Equal(1, len(nodes["b"])) @@ -185,8 +182,8 @@ func parseM2M(req *require.Assertions) { val := reflect.ValueOf(a1) - err := parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations, &oldRels, &ids, &nodeRef) - req.Nil(err) + req.Nil(parseStruct("", "", false, 0, nil, &val, 0, 5, &nodes, &relations, &oldRels, &ids, &nodeRef)) + req.Nil(generateCurRels("", &val, 0, 5, &curRels)) req.Equal(2, len(nodes)) req.Equal(1, len(nodes["a"])) req.Equal(1, len(nodes["b"])) @@ -324,77 +321,4 @@ func TestCalculateDels(t *testing.T) { req.EqualValues(map[string][]int64{}, dels) } -func TestSave(t *testing.T) { - t.Skip() - req := require.New(t) - - conf := Config{ - Username: "neo4j", - Password: "password", - Host: "0.0.0.0", - Port: 7687, - PoolSize: 15, - IndexStrategy: IGNORE_INDEX, - } - - req.Nil(Init(&conf, &a{}, &b{}, &c{})) - - a2 := &a{ - TestField: "test", - } - - b2 := &b{ - TestField: "test", - TestTime: time.Now().UTC(), - } - - b3 := &b{ - TestField: "dasdfasd", - } - - c1 := &c{ - Start: a2, - End: b2, - Test: "testing", - } - - a2.SingleSpecA = c1 - a2.ManyA = []*b{b3} - b2.SingleSpec = c1 - b3.ManyB = a2 - - conn, err := driverPool.Open(driver.ReadWriteMode) - if err != nil { - require.Nil(t, err) - } - defer driverPool.Reclaim(conn) - req.Nil(saveDepth(conn, a2, 5)) - req.EqualValues(map[string]*RelationConfig{ - "SingleSpecA": { - Ids: []int64{b2.Id}, - RelationType: Single, - }, - "ManyA": { - Ids: []int64{b3.Id}, - RelationType: Multi, - }, - }, a2.LoadMap) - req.EqualValues(map[string]*RelationConfig{ - "SingleSpec": { - Ids: []int64{a2.Id}, - RelationType: Single, - }, - }, b2.LoadMap) - req.EqualValues(map[string]*RelationConfig{ - "ManyB": { - Ids: []int64{a2.Id}, - RelationType: Single, - }, - }, b3.LoadMap) - a2.SingleSpecA = nil - b2.SingleSpec = nil - - req.Nil(saveDepth(conn, a2, 5)) - log.Println("done") -} diff --git a/session_test.go b/session_test.go deleted file mode 100644 index 0bc3578..0000000 --- a/session_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package gogm - -import ( - "github.com/stretchr/testify/require" - "testing" -) - -func TestSession(t *testing.T) { - if !testing.Short() { - t.Skip() - return - } - - req := require.New(t) - - conf := Config{ - Username: "neo4j", - Password: "password", - Host: "0.0.0.0", - Port: 7687, - PoolSize: 15, - IndexStrategy: VALIDATE_INDEX, - } - - req.Nil(Init(&conf, &a{}, &b{}, &c{})) - - req.EqualValues(3, mappedTypes.Len()) - - sess, err := NewSession(true) - req.NotNil(err) - - var stuffs []a - - req.Nil(sess.LoadAll(&stuffs)) - - req.EqualValues(1, len(stuffs)) -} From 015f991a1f7434ef0bcdecdefe8a49c27b86beca Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Wed, 4 Dec 2019 16:42:45 -0500 Subject: [PATCH 14/27] actions test 1 --- .github/{workflows => }/docker-compose.yaml | 0 .github/workflows/go.yml | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename .github/{workflows => }/docker-compose.yaml (100%) diff --git a/.github/workflows/docker-compose.yaml b/.github/docker-compose.yaml similarity index 100% rename from .github/workflows/docker-compose.yaml rename to .github/docker-compose.yaml diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 4410d3b..13b3475 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -21,9 +21,9 @@ jobs: dep ensure fi - name: Build - run: go build -v . + run: go build -v . - name: Run Unit Tests - run: go test ./... -short + run: go test ./... -short - name: Start Neo4j Docker run: | docker-compose up -d From 0c093900dddb5c8d2b847edcd84ae3fd74372e83 Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Wed, 4 Dec 2019 16:46:28 -0500 Subject: [PATCH 15/27] actions test 2 --- .github/workflows/go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 13b3475..acb0533 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -26,7 +26,7 @@ jobs: run: go test ./... -short - name: Start Neo4j Docker run: | - docker-compose up -d + docker-compose -f .github/docker-compose.yaml up -d - name: Wait for neo4j to be ready run: | end="$((SECONDS+60))" From 510634814c11d95bc12a29afb3940fa808c187df Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Wed, 4 Dec 2019 16:48:44 -0500 Subject: [PATCH 16/27] actions test 3 --- .github/workflows/go.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index acb0533..2d15fe3 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -35,6 +35,8 @@ jobs: [[ "${SECONDS}" -ge "${end}" ]] && exit 1 sleep 1 done + docker ps + docker logs github_neo_1 - name: Run Integration Test run: go test ./... -run Integration - name: Stop Neo4j Docker From 66021be87fa45987038359bbcb9b965bfc25fabd Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Wed, 4 Dec 2019 16:50:39 -0500 Subject: [PATCH 17/27] actions test 4 --- .github/workflows/go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 2d15fe3..58ffd3e 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -36,7 +36,7 @@ jobs: sleep 1 done docker ps - docker logs github_neo_1 + sleep 5 - name: Run Integration Test run: go test ./... -run Integration - name: Stop Neo4j Docker From 16948eac7d61b0b64d231d83b57dd3a28d960f09 Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Wed, 4 Dec 2019 16:52:57 -0500 Subject: [PATCH 18/27] actions test 5 --- .github/workflows/go.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 58ffd3e..8fcd4d3 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -29,17 +29,10 @@ jobs: docker-compose -f .github/docker-compose.yaml up -d - name: Wait for neo4j to be ready run: | - end="$((SECONDS+60))" - while true; do - nc -w 2 localhost 7687 && break - [[ "${SECONDS}" -ge "${end}" ]] && exit 1 - sleep 1 - done - docker ps sleep 5 - name: Run Integration Test run: go test ./... -run Integration - name: Stop Neo4j Docker run: | - docker-compose down + docker-compose -f .github/docker-compose.yaml down From 0ddfa34157923ebf7ed2bd5e007de08162ed4aaf Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Wed, 4 Dec 2019 17:19:44 -0500 Subject: [PATCH 19/27] update readme --- README.md | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7c13889..a9589e1 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ go get -u github.com/mindstand/gogm - Support for HA Clusters using `bolt+routing` through [MindStand's fork](https://github.com/mindstand/golang-neo4j-bolt-driver) of [@johnnadratowski's golang bolt driver](https://github.com/johnnadratowski/golang-neo4j-bolt-driver) - Custom queries in addition to built in functionality - Builder pattern cypher queries using [MindStand's cypher dsl package](https://github.com/mindstand/go-cypherdsl) +- CLI to generate link and unlink functions for gogm structs. ## Usage @@ -160,17 +161,42 @@ func main(){ ``` +### GoGM CLI + +## CLI Installation +``` +go get -u github.com/mindstand/gogm/cli/gogmcli +``` + +## CLI Usage +``` +NAME: + gogmcli - used for neo4j operations from gogm schema + +USAGE: + gogmcli [global options] command [command options] [arguments...] + +VERSION: + 1.0.0 + +COMMANDS: + generate, g, gen to generate link and unlink functions for nodes + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS: + --debug, -d execute in debug mode (default: false) + --help, -h show help (default: false) + --version, -v print the version (default: false) +``` + ## Inspiration Inspiration came from the Java OGM implementation by Neo4j. ## Road Map -- More validation (refer to issues #2, #8) - Schema Migration -- Generation CLI for link functions - Errors overhaul using go 1.13 error wrapping - TLS Support - Documentation (obviously) -- More to come as we find more bugs! ## Credits - [adam hannah's arrayOperations](https://github.com/adam-hanna/arrayOperations) From 3c2bb44973084c4cab43b4b36678af7a78614ac3 Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Wed, 4 Dec 2019 17:21:20 -0500 Subject: [PATCH 20/27] fix gogmcli version number --- cmd/gogmcli/gogm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/gogmcli/gogm.go b/cmd/gogmcli/gogm.go index 026e25d..316b9e3 100644 --- a/cmd/gogmcli/gogm.go +++ b/cmd/gogmcli/gogm.go @@ -14,7 +14,7 @@ func main() { app := &cli.App{ Name: "gogmcli", HelpName: "gogmcli", - Version: "0.2.0", + Version: "1.0.0", Usage: "used for neo4j operations from gogm schema", Description: "cli for generating and executing migrations with gogm", EnableBashCompletion: true, From b38bc2d403c6cc7cbb4f1ea5678026f904386332 Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Wed, 4 Dec 2019 17:23:22 -0500 Subject: [PATCH 21/27] gofmt --- delete_test.go | 1 - save_test.go | 2 -- 2 files changed, 3 deletions(-) diff --git a/delete_test.go b/delete_test.go index 6e3f484..3d84903 100644 --- a/delete_test.go +++ b/delete_test.go @@ -3,7 +3,6 @@ package gogm import ( driver "github.com/mindstand/golang-neo4j-bolt-driver" "github.com/stretchr/testify/require" - ) func testDelete(req *require.Assertions) { diff --git a/save_test.go b/save_test.go index b63ca88..ba7de88 100644 --- a/save_test.go +++ b/save_test.go @@ -320,5 +320,3 @@ func TestCalculateDels(t *testing.T) { req.EqualValues(map[string][]int64{}, dels) } - - From 180b915ef4645bf278b65bfcc0b4f79bb1e50442 Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Thu, 5 Dec 2019 10:16:14 -0500 Subject: [PATCH 22/27] bug fixes defined by pr --- cmd/gogmcli/gen/gen.go | 149 +++++++++++++++++++++------------------ cmd/gogmcli/gen/parse.go | 8 +-- cmd/gogmcli/util/util.go | 2 +- 3 files changed, 83 insertions(+), 76 deletions(-) diff --git a/cmd/gogmcli/gen/gen.go b/cmd/gogmcli/gen/gen.go index 1dfdffa..18cb2e1 100644 --- a/cmd/gogmcli/gen/gen.go +++ b/cmd/gogmcli/gen/gen.go @@ -9,6 +9,7 @@ import ( "html/template" "log" "os" + "path" "path/filepath" "strings" ) @@ -19,7 +20,7 @@ func Generate(directory string, debug bool) error { var edges []string packageName := "" - err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error { + err := filepath.Walk(directory, func(filePath string, info os.FileInfo, err error) error { if err != nil { return err } @@ -28,29 +29,29 @@ func Generate(directory string, debug bool) error { return errors.New("file info is nil") } - if info.IsDir() && path != directory { + if info.IsDir() && filePath != directory { if debug { - log.Printf("skipping [%s] as it is a directory\n", path) + log.Printf("skipping [%s] as it is a directory\n", filePath) } return filepath.SkipDir } - if strings.Contains(path, ".go") { + if path.Ext(filePath) == ".go" { if debug { - log.Printf("parsing go file [%s]\n", path) + log.Printf("parsing go file [%s]\n", filePath) } - err := parseFile(path, &confs, &edges, imps, &packageName) + err := parseFile(filePath, &confs, &edges, imps, &packageName) if err != nil { if debug { - log.Printf("failed to parse go file [%s] with error '%s'\n", path, err.Error()) + log.Printf("failed to parse go file [%s] with error '%s'\n", filePath, err.Error()) } return err } if debug { - log.Printf("successfully parsed go file [%s]\n", path) + log.Printf("successfully parsed go file [%s]\n", filePath) } } else if debug { - log.Printf("skipping non go file [%s]\n", path) + log.Printf("skipping non go file [%s]\n", filePath) } return nil @@ -65,7 +66,7 @@ func Generate(directory string, debug bool) error { imports = append(imports, imp...) } - imports = util.SliceUniqMap(imports) + imports = util.RemoveDuplicates(imports) for i := 0; i < len(imports); i++ { imports[i] = strings.Replace(imports[i], "\"", "", -1) @@ -116,65 +117,9 @@ func Generate(directory string, debug bool) error { isSpecialEdge = true } - searchLoop: - for _, lookup := range rels { - //check special edge - if rel.Type != lookup.NodeName && !isSpecialEdge { - continue - } - - switch rel.Direction { - case dsl.DirectionOutgoing: - if lookup.Direction == dsl.DirectionIncoming { - tplRel.OtherStructField = lookup.Field - tplRel.OtherStructFieldIsMany = lookup.IsMany - if isSpecialEdge { - tplRel.OtherStructName = lookup.NodeName - } - break searchLoop - } else { - continue - } - - case dsl.DirectionIncoming: - if lookup.Direction == dsl.DirectionOutgoing { - tplRel.OtherStructField = lookup.Field - tplRel.OtherStructFieldIsMany = lookup.IsMany - if isSpecialEdge { - tplRel.OtherStructName = lookup.NodeName - } - break searchLoop - } else { - continue - } - - case dsl.DirectionNone: - if lookup.Direction == dsl.DirectionNone { - tplRel.OtherStructField = lookup.Field - tplRel.OtherStructFieldIsMany = lookup.IsMany - if isSpecialEdge { - tplRel.OtherStructName = lookup.NodeName - } - break searchLoop - } else { - continue - } - - case dsl.DirectionBoth: - if lookup.Direction == dsl.DirectionBoth { - tplRel.OtherStructField = lookup.Field - tplRel.OtherStructFieldIsMany = lookup.IsMany - if isSpecialEdge { - tplRel.OtherStructName = lookup.NodeName - } - break searchLoop - } else { - continue - } - - default: - return fmt.Errorf("invalid direction [%v]", rel.Direction) - } + err = parseDirection(rel, rels, tplRel, isSpecialEdge) + if err != nil { + return err } if tplRel.OtherStructField == "" { @@ -219,7 +164,7 @@ func Generate(directory string, debug bool) error { return err } - f, err := os.Create(fmt.Sprintf("%s/linking.go", directory)) + f, err := os.Create(path.Join(directory, "linking.go")) if err != nil { return err } @@ -242,3 +187,67 @@ func Generate(directory string, debug bool) error { return nil } + +func parseDirection(rel *relConf, rels []*relConf, tplRel *tplRelConf, isSpecialEdge bool) error { + for _, lookup := range rels { + //check special edge + if rel.Type != lookup.NodeName && !isSpecialEdge { + continue + } + + switch rel.Direction { + case dsl.DirectionOutgoing: + if lookup.Direction == dsl.DirectionIncoming { + tplRel.OtherStructField = lookup.Field + tplRel.OtherStructFieldIsMany = lookup.IsMany + if isSpecialEdge { + tplRel.OtherStructName = lookup.NodeName + } + return nil + } else { + continue + } + + case dsl.DirectionIncoming: + if lookup.Direction == dsl.DirectionOutgoing { + tplRel.OtherStructField = lookup.Field + tplRel.OtherStructFieldIsMany = lookup.IsMany + if isSpecialEdge { + tplRel.OtherStructName = lookup.NodeName + } + return nil + } else { + continue + } + + case dsl.DirectionNone: + if lookup.Direction == dsl.DirectionNone { + tplRel.OtherStructField = lookup.Field + tplRel.OtherStructFieldIsMany = lookup.IsMany + if isSpecialEdge { + tplRel.OtherStructName = lookup.NodeName + } + return nil + } else { + continue + } + + case dsl.DirectionBoth: + if lookup.Direction == dsl.DirectionBoth { + tplRel.OtherStructField = lookup.Field + tplRel.OtherStructFieldIsMany = lookup.IsMany + if isSpecialEdge { + tplRel.OtherStructName = lookup.NodeName + } + return nil + } else { + continue + } + + default: + return fmt.Errorf("invalid direction [%v]", rel.Direction) + } + } + + return nil +} \ No newline at end of file diff --git a/cmd/gogmcli/gen/parse.go b/cmd/gogmcli/gen/parse.go index e39d5b2..901c1de 100644 --- a/cmd/gogmcli/gen/parse.go +++ b/cmd/gogmcli/gen/parse.go @@ -21,6 +21,7 @@ type relConf struct { Direction go_cypherdsl.Direction } +// parses each file using ast func parseFile(filePath string, confs *map[string][]*relConf, edges *[]string, imports map[string][]string, packageName *string) error { fset := token.NewFileSet() node, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments) @@ -58,6 +59,7 @@ func parseFile(filePath string, confs *map[string][]*relConf, edges *[]string, i return err } + // if its not an edge, parse it as a gogm struct if !isEdge { (*confs)[label] = []*relConf{} err = parseGogmNode(strType, confs, label, fset) @@ -144,11 +146,7 @@ func parseGogmEdge(node *ast.File, label string) (bool, error) { } } //check if its an edge node - if !GetStartNode || !GetStartNodeType || !SetStartNode || !GetEndNode || !GetEndNodeType || !SetEndNode { - return false, nil - } - - return true, nil + return !GetStartNode || !GetStartNodeType || !SetStartNode || !GetEndNode || !GetEndNodeType || !SetEndNode, nil } func parseGogmNode(strType *ast.StructType, confs *map[string][]*relConf, label string, fset *token.FileSet) error { diff --git a/cmd/gogmcli/util/util.go b/cmd/gogmcli/util/util.go index 95b63a7..fb4c238 100644 --- a/cmd/gogmcli/util/util.go +++ b/cmd/gogmcli/util/util.go @@ -1,6 +1,6 @@ package util -func SliceUniqMap(s []string) []string { +func RemoveDuplicates(s []string) []string { if s == nil { return []string{} } From 517a007a98b9b89b07cf3a045f5b7390ebf4f1d9 Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Thu, 5 Dec 2019 10:19:05 -0500 Subject: [PATCH 23/27] readme --- testing_/readme.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 testing_/readme.md diff --git a/testing_/readme.md b/testing_/readme.md new file mode 100644 index 0000000..ffdc784 --- /dev/null +++ b/testing_/readme.md @@ -0,0 +1 @@ +This directory is used to test link and unlink generator from gogmcli. `linking.go` is generated by the gogmcli. \ No newline at end of file From 042536fc19e9384193ec6cf331f6cdc552126f64 Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Thu, 5 Dec 2019 13:42:57 -0500 Subject: [PATCH 24/27] added licence and godoc stuff --- .github/docker-compose.yaml | 19 + .github/workflows/go.yml | 19 + cmd/gogmcli/gen/gen.go | 24 ++ cmd/gogmcli/gen/parse.go | 23 +- cmd/gogmcli/gen/templ.go | 19 + cmd/gogmcli/gogm.go | 20 + cmd/gogmcli/util/util.go | 20 + config.go | 39 +- config_test.go | 19 + decoder.go | 29 +- decoder_test.go | 19 + decorator.go | 82 +++- decorator_test.go | 19 + defaults.go | 32 ++ delete.go | 22 ++ delete_test.go | 19 + errors.go | 22 ++ go.mod | 4 +- go.sum | 4 + index.go | 25 +- index_test.go | 19 + integration_test.go | 19 + interface.go | 32 ++ load_strategy.go | 25 ++ load_strategy_test.go | 19 + mocks/ISession.go | 19 + model.go | 29 +- pagination.go | 25 ++ save.go | 41 +- save_test.go | 19 + session.go | 19 + testing_/linking.go | 19 + testing_/linking_test.go | 19 + testing_/test_edge.go | 19 + testing_/test_obj.go | 19 + testing_/test_obj2.go | 19 + testing_/test_omit.go | 19 + util.go | 24 ++ util/arrayOperations.go | 716 ----------------------------------- util/arrayOperations_test.go | 605 ----------------------------- util_test.go | 19 + 41 files changed, 878 insertions(+), 1345 deletions(-) delete mode 100644 util/arrayOperations.go delete mode 100644 util/arrayOperations_test.go diff --git a/.github/docker-compose.yaml b/.github/docker-compose.yaml index f43e866..3583ccc 100644 --- a/.github/docker-compose.yaml +++ b/.github/docker-compose.yaml @@ -1,3 +1,22 @@ +# Copyright (c) 2019 MindStand Technologies, Inc +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + version: "3.6" services: neo: diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 8fcd4d3..a69e051 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -1,3 +1,22 @@ +# Copyright (c) 2019 MindStand Technologies, Inc +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + name: Go on: [push] jobs: diff --git a/cmd/gogmcli/gen/gen.go b/cmd/gogmcli/gen/gen.go index 18cb2e1..6b465d4 100644 --- a/cmd/gogmcli/gen/gen.go +++ b/cmd/gogmcli/gen/gen.go @@ -1,3 +1,23 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// gen provides code to generate link and unlink functions for gogm structs package gen import ( @@ -14,6 +34,9 @@ import ( "strings" ) +// Generate searches for all go source files, then generates link and unlink functions for all gogm structs +// takes in root directory and whether to log in debug mode +// note: Generate is not recursive, it only looks in the target directory func Generate(directory string, debug bool) error { confs := map[string][]*relConf{} imps := map[string][]string{} @@ -188,6 +211,7 @@ func Generate(directory string, debug bool) error { return nil } +// parseDirection parses gogm struct tags and writes to a holder struct func parseDirection(rel *relConf, rels []*relConf, tplRel *tplRelConf, isSpecialEdge bool) error { for _, lookup := range rels { //check special edge diff --git a/cmd/gogmcli/gen/parse.go b/cmd/gogmcli/gen/parse.go index 901c1de..3ab8e4b 100644 --- a/cmd/gogmcli/gen/parse.go +++ b/cmd/gogmcli/gen/parse.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gen import ( @@ -21,7 +40,7 @@ type relConf struct { Direction go_cypherdsl.Direction } -// parses each file using ast +// parses each file using ast looking for nodes to handle func parseFile(filePath string, confs *map[string][]*relConf, edges *[]string, imports map[string][]string, packageName *string) error { fset := token.NewFileSet() node, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments) @@ -77,6 +96,7 @@ func parseFile(filePath string, confs *map[string][]*relConf, edges *[]string, i return nil } +//parseGogmEdge: checks if node implements `IEdge` func parseGogmEdge(node *ast.File, label string) (bool, error) { if node == nil { return false, errors.New("node can not be nil") @@ -149,6 +169,7 @@ func parseGogmEdge(node *ast.File, label string) (bool, error) { return !GetStartNode || !GetStartNodeType || !SetStartNode || !GetEndNode || !GetEndNodeType || !SetEndNode, nil } +// parseGogmNode generates configuration for GoGM struct func parseGogmNode(strType *ast.StructType, confs *map[string][]*relConf, label string, fset *token.FileSet) error { if strType.Fields != nil && strType.Fields.List != nil && len(strType.Fields.List) != 0 { fieldLoop: diff --git a/cmd/gogmcli/gen/templ.go b/cmd/gogmcli/gen/templ.go index 0e5160b..dd158df 100644 --- a/cmd/gogmcli/gen/templ.go +++ b/cmd/gogmcli/gen/templ.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gen //expect .StructName .OtherStructName .StructField .OtherStructField .StructFieldIsMany .OtherStructFieldIsMany diff --git a/cmd/gogmcli/gogm.go b/cmd/gogmcli/gogm.go index 316b9e3..d7f6e2e 100644 --- a/cmd/gogmcli/gogm.go +++ b/cmd/gogmcli/gogm.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package main import ( @@ -8,6 +27,7 @@ import ( "os" ) +//main is the main function func main() { var debug bool diff --git a/cmd/gogmcli/util/util.go b/cmd/gogmcli/util/util.go index fb4c238..051158f 100644 --- a/cmd/gogmcli/util/util.go +++ b/cmd/gogmcli/util/util.go @@ -1,5 +1,25 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package util +// RemoveDuplicates removes duplicates from string slice func RemoveDuplicates(s []string) []string { if s == nil { return []string{} diff --git a/config.go b/config.go index a9a44f6..c0ad1ba 100644 --- a/config.go +++ b/config.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gogm import ( @@ -24,6 +43,7 @@ func getLogger() *logrus.Entry { return externalLog } +// SetLogger sets logrus logger func SetLogger(logger *logrus.Entry) error { if logger == nil { return errors.New("logger can not be nil") @@ -32,20 +52,29 @@ func SetLogger(logger *logrus.Entry) error { return nil } +// Config Defined GoGM config type Config struct { + // Host is the neo4j host Host string `yaml:"host" json:"host"` + // Port is the neo4j port Port int `yaml:"port" json:"port"` + // IsCluster specifies whether GoGM is connecting to a casual cluster or not IsCluster bool `yaml:"is_cluster" json:"is_cluster"` + // Username is the GoGM username Username string `yaml:"username" json:"username"` + // Password is the GoGM password Password string `yaml:"password" json:"password"` + // PoolSize is the size of the connection pool for GoGM PoolSize int `yaml:"pool_size" json:"pool_size"` + // Index Strategy defines the index strategy for GoGM IndexStrategy IndexStrategy `yaml:"index_strategy" json:"index_strategy"` } +// ConnectionString builds the neo4j bolt/bolt+routing connection string func (c *Config) ConnectionString() string { var protocol string @@ -58,15 +87,19 @@ func (c *Config) ConnectionString() string { return fmt.Sprintf("%s://%s:%s@%s:%v", protocol, c.Username, c.Password, c.Host, c.Port) } +// Index Strategy typedefs int to define different index approaches type IndexStrategy int const ( + // Assert Index ensures that all indices are set and sets them if they are not there ASSERT_INDEX IndexStrategy = 0 + // Validate Index ensures that all indices are set VALIDATE_INDEX IndexStrategy = 1 + // Ignore Index skips the index step of setup IGNORE_INDEX IndexStrategy = 2 ) -//convert these into concurrent hashmap +//holds mapped types var mappedTypes = &hashmap.HashMap{} //thread pool @@ -81,16 +114,20 @@ func makeRelMapKey(start, edge, direction, rel string) string { var isSetup = false +// Init sets up gogm. Takes in config object and variadic slice of gogm nodes to map. +// Note: Must pass pointers to nodes! func Init(conf *Config, mapTypes ...interface{}) error { return setupInit(false, conf, mapTypes...) } +// Resets GoGM configuration func Reset() { mappedTypes = &hashmap.HashMap{} mappedRelations = &relationConfigs{} isSetup = false } +// internal setup logic for gogm func setupInit(isTest bool, conf *Config, mapTypes ...interface{}) error { if isSetup && !isTest { return errors.New("gogm has already been initialized") diff --git a/config_test.go b/config_test.go index 8b79b3c..5c718d6 100644 --- a/config_test.go +++ b/config_test.go @@ -1 +1,20 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gogm diff --git a/decoder.go b/decoder.go index abcde1b..8379420 100644 --- a/decoder.go +++ b/decoder.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gogm import ( @@ -11,6 +30,7 @@ import ( "time" ) +// decodes neo4j rows and writes the response to generic interface func decodeNeoRows(rows neo.Rows, respObj interface{}) error { defer rows.Close() @@ -22,8 +42,8 @@ func decodeNeoRows(rows neo.Rows, respObj interface{}) error { return decode(arr, respObj) } -//example query `match p=(n)-[*0..5]-() return p` //decodes raw path response from driver +//example query `match p=(n)-[*0..5]-() return p` func decode(rawArr [][]interface{}, respObj interface{}) (err error) { //check nil params if rawArr == nil { @@ -321,6 +341,7 @@ func decode(rawArr [][]interface{}, respObj interface{}) (err error) { } } +// getPrimaryLabel gets the label from a reflect type func getPrimaryLabel(rt reflect.Type) string { //assume its already a pointer rt = rt.Elem() @@ -335,6 +356,7 @@ func getPrimaryLabel(rt reflect.Type) string { return rt.Name() } +// sortIsolatedNodes process nodes that are returned individually from bolt driver func sortIsolatedNodes(isolatedNodes []*graph.Node, labelLookup *map[int64]string, nodeLookup *map[int64]*reflect.Value, pks *[]int64, pkLabel string, relMaps *map[int64]map[string]*RelationConfig) error { if isolatedNodes == nil { return fmt.Errorf("isolatedNodes can not be nil, %w", ErrInternal) @@ -371,6 +393,7 @@ func sortIsolatedNodes(isolatedNodes []*graph.Node, labelLookup *map[int64]strin return nil } +// sortStrictRels sorts relationships that are strictly defined (i.e direction is pre defined) from the bolt driver func sortStrictRels(strictRels []*graph.Relationship, labelLookup *map[int64]string, rels *map[int64]*neoEdgeConfig) error { if strictRels == nil { return fmt.Errorf("paths is empty, that shouldn't have happened, %w", ErrInternal) @@ -407,6 +430,7 @@ func sortStrictRels(strictRels []*graph.Relationship, labelLookup *map[int64]str return nil } +// sortPaths sorts nodes and relationships from bolt driver that dont specify the direction explicitly, instead uses the bolt spec to determine direction func sortPaths(paths []*graph.Path, nodeLookup *map[int64]*reflect.Value, rels *map[int64]*neoEdgeConfig, pks *[]int64, pkLabel string, relMaps *map[int64]map[string]*RelationConfig) error { if paths == nil { return fmt.Errorf("paths is empty, that shouldn't have happened, %w", ErrInternal) @@ -489,6 +513,7 @@ func sortPaths(paths []*graph.Path, nodeLookup *map[int64]*reflect.Value, rels * return nil } +// getValueAndConfig returns reflect value of specific node and the configuration for the node func getValueAndConfig(id int64, t string, nodeLookup map[int64]*reflect.Value) (val *reflect.Value, conf structDecoratorConfig, err error) { var ok bool @@ -513,6 +538,7 @@ func getValueAndConfig(id int64, t string, nodeLookup map[int64]*reflect.Value) var sliceOfEmptyInterface []interface{} var emptyInterfaceType = reflect.TypeOf(sliceOfEmptyInterface).Elem() +// convertToValue converts properties map from neo4j to golang reflect value func convertToValue(graphId int64, conf structDecoratorConfig, props map[string]interface{}, rtype reflect.Type) (valss *reflect.Value, err error) { defer func() { if r := recover(); r != nil { @@ -625,6 +651,7 @@ func convertToValue(graphId int64, conf structDecoratorConfig, props map[string] return &val, err } +// convertNodeToValue converts raw bolt node to reflect value func convertNodeToValue(boltNode graph.Node) (*reflect.Value, error) { if boltNode.Labels == nil || len(boltNode.Labels) == 0 { diff --git a/decoder_test.go b/decoder_test.go index 4a006d6..26bdc6b 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gogm import ( diff --git a/decorator.go b/decorator.go index 7966d7e..87257ca 100644 --- a/decorator.go +++ b/decorator.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gogm import ( @@ -9,43 +28,86 @@ import ( "time" ) +// defined the decorator name for struct tag const decoratorName = "gogm" +// reflect type for go time.Time var timeType = reflect.TypeOf(time.Time{}) //sub fields of the decorator const ( - paramNameField = "name" //requires assignment - relationshipNameField = "relationship" //requires assignment - directionField = "direction" //requires assignment + // specifies the name in neo4j + //requires assignment (if specified) + paramNameField = "name" + + // specifies the name of the relationship + //requires assignment (if edge field) + relationshipNameField = "relationship" + + //specifies direction, can only be (incoming|outgoing|both|none) + //requires assignment (if edge field) + directionField = "direction" + + //specifies if the field contains time representation timeField = "time" + + //specifies if the field is to be indexed indexField = "index" + + //specifies if the field is unique uniqueField = "unique" + + //specifies is the field is a primary key primaryKeyField = "pk" + + //specifies if the field is map of type `map[string]interface{}` propertiesField = "properties" + + //specifies if the field is to be ignored ignoreField = "-" + + //specifies deliminator between GoGM tags deliminator = ";" + + //assignment operator for GoGM tags assignmentOperator = "=" ) +//decorator config defines configuration of GoGM field type decoratorConfig struct { + // holds reflect type for the field Type reflect.Type `json:"-"` + // holds the name of the field for neo4j Name string `json:"name"` + // holds the name of the field in the struct FieldName string `json:"field_name"` + // holds the name of the relationship Relationship string `json:"relationship"` + // holds the direction Direction dsl.Direction `json:"direction"` + // specifies if field is to be unique Unique bool `json:"unique"` + // specifies if field is to be indexed Index bool `json:"index"` + // specifies if field represents many relationship ManyRelationship bool `json:"many_relationship"` + // uses edge specifies if the edge is a special node UsesEdgeNode bool `json:"uses_edge_node"` + // specifies whether the field is the nodes primary key PrimaryKey bool `json:"primary_key"` + // specify if the field holds properties Properties bool `json:"properties"` + // specifies if the field contains time value IsTime bool `json:"is_time"` + // specifies if the field contains a typedef of another type IsTypeDef bool `json:"is_type_def"` + // holds the reflect type of the root type if typedefed TypedefActual reflect.Type `json:"-"` + // specifies whether to ignore the field Ignore bool `json:"ignore"` } +// Equals checks equality of decorator configs func (d *decoratorConfig) Equals(comp *decoratorConfig) bool { if comp == nil { return false @@ -57,14 +119,20 @@ func (d *decoratorConfig) Equals(comp *decoratorConfig) bool { d.IsTypeDef == comp.IsTypeDef && d.Ignore == comp.Ignore } +// specifies configuration on GoGM node type structDecoratorConfig struct { + // Holds fields -> their configurations // field name : decorator configuration Fields map[string]decoratorConfig `json:"fields"` + // holds label for the node, maps to struct name Label string `json:"label"` + // specifies if the node is a vertex or an edge (if true, its a vertex) IsVertex bool `json:"is_vertex"` + // holds the reflect type of the struct Type reflect.Type `json:"-"` } +// Equals checks equality of structDecoratorConfigs func (s *structDecoratorConfig) Equals(comp *structDecoratorConfig) bool { if comp == nil { return false @@ -87,7 +155,7 @@ func (s *structDecoratorConfig) Equals(comp *structDecoratorConfig) bool { return s.IsVertex == comp.IsVertex && s.Label == comp.Label } -//have struct validate itself +// Validate checks if the configuration is valid func (d *decoratorConfig) Validate() error { if d.Ignore { if d.Relationship != "" || d.Unique || d.Index || d.ManyRelationship || d.UsesEdgeNode || @@ -207,6 +275,8 @@ func (d *decoratorConfig) Validate() error { var edgeType = reflect.TypeOf(new(IEdge)).Elem() +// newDecoratorConfig generates decorator config for field +// takes in the raw tag, name of the field and reflect type func newDecoratorConfig(decorator, name string, varType reflect.Type) (*decoratorConfig, error) { fields := strings.Split(decorator, deliminator) @@ -339,7 +409,7 @@ func newDecoratorConfig(decorator, name string, varType reflect.Type) (*decorato return &toReturn, nil } -//validates struct configuration +//validates if struct decorator is valid func (s *structDecoratorConfig) Validate() error { if s.Fields == nil { return errors.New("no fields defined") @@ -375,6 +445,7 @@ func (s *structDecoratorConfig) Validate() error { return nil } +// getStructDecoratorConfig generates structDecoratorConfig for struct func getStructDecoratorConfig(i interface{}, mappedRelations *relationConfigs) (*structDecoratorConfig, error) { toReturn := &structDecoratorConfig{} @@ -496,6 +567,7 @@ func getStructDecoratorConfig(i interface{}, mappedRelations *relationConfigs) ( return toReturn, nil } +// getFields gets all fields in a struct, specifically also gets fields from embedded structs func getFields(val reflect.Type) []*reflect.StructField { var fields []*reflect.StructField if val.Kind() == reflect.Ptr { diff --git a/decorator_test.go b/decorator_test.go index 7f1b46f..fc5c270 100644 --- a/decorator_test.go +++ b/decorator_test.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gogm import ( diff --git a/defaults.go b/defaults.go index 57b2152..62079ce 100644 --- a/defaults.go +++ b/defaults.go @@ -1,22 +1,54 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gogm const loadMapField = "LoadMap" +// BaseNode contains fields that ALL GoGM nodes are required to have type BaseNode struct { + // Id is the GraphId that neo4j uses internally Id int64 `json:"-" gogm:"name=id"` + // UUID is the unique identifier GoGM uses as a primary key UUID string `json:"uuid" gogm:"pk;name=uuid"` + + // LoadMap represents the state of how a node was loaded for neo4j. + // This is used to determine if relationships are removed on save // field -- relations LoadMap map[string]*RelationConfig `json:"-" gogm:"-"` } +// Specifies Type of Relationship type RelationType int const ( + // Side of relationship can only point to 0 or 1 other nodes Single RelationType = 0 + + // Side of relationship can point to 0+ other nodes Multi RelationType = 1 ) +// RelationConfig specifies how relationships are loaded type RelationConfig struct { + // stores graph ids Ids []int64 `json:"-" gomg:"-"` + // specifies relationship type RelationType RelationType `json:"-" gomg:"-"` } diff --git a/delete.go b/delete.go index 29c859f..4091b1c 100644 --- a/delete.go +++ b/delete.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gogm import ( @@ -7,6 +26,7 @@ import ( "reflect" ) +// deleteNode is used to remove nodes from the database func deleteNode(conn *driver.BoltConn, deleteObj interface{}) error { rawType := reflect.TypeOf(deleteObj) @@ -55,6 +75,7 @@ func deleteNode(conn *driver.BoltConn, deleteObj interface{}) error { return deleteByIds(conn, ids...) } +// deleteByIds deletes node by graph ids func deleteByIds(conn *driver.BoltConn, ids ...int64) error { rows, err := dsl.QB(). Cypher("UNWIND {rows} as row"). @@ -83,6 +104,7 @@ func deleteByIds(conn *driver.BoltConn, ids ...int64) error { return nil } +// deleteByUuids deletes nodes by uuids func deleteByUuids(conn *driver.BoltConn, ids ...string) error { rows, err := dsl.QB(). Cypher("UNWIND {rows} as row"). diff --git a/delete_test.go b/delete_test.go index 3d84903..9861b9f 100644 --- a/delete_test.go +++ b/delete_test.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gogm import ( diff --git a/errors.go b/errors.go index cbf835e..d01f3f2 100644 --- a/errors.go +++ b/errors.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gogm import ( @@ -5,6 +24,8 @@ import ( "fmt" ) +// todo replace this with go 1.13 errors + type InvalidDecoratorConfigError struct { Field string Issue string @@ -35,6 +56,7 @@ func (i *InvalidStructConfigError) Error() string { return i.issue } +// base errors for gogm 1.13 errors, these are pretty self explanatory var ErrNotFound = errors.New("gogm: data not found") var ErrInternal = errors.New("gogm: internal error") var ErrValidation = errors.New("gogm: struct validation error") diff --git a/go.mod b/go.mod index de55f15..535b01e 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,11 @@ module github.com/mindstand/gogm go 1.13 require ( + github.com/adam-hanna/arrayOperations v0.2.5 github.com/cornelk/hashmap v1.0.0 github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/dchest/siphash v1.2.1 // indirect + github.com/google/addlicense v0.0.0-20190907113143-be125746c2c4 // indirect github.com/google/uuid v1.1.1 github.com/kr/pretty v0.1.0 // indirect github.com/mindstand/go-cypherdsl v0.0.0-20191030200322-ed2619be6449 @@ -14,7 +16,7 @@ require ( github.com/sirupsen/logrus v1.4.2 github.com/stretchr/objx v0.2.0 // indirect github.com/stretchr/testify v1.4.0 - github.com/urfave/cli v1.22.2 + github.com/urfave/cli v1.22.2 // indirect github.com/urfave/cli/v2 v2.0.0 golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect diff --git a/go.sum b/go.sum index a7ea972..e65c465 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/adam-hanna/arrayOperations v0.2.5 h1:zphKpB5HGhHDkztF2oLcvnqIAu/L/YU3FB/9UghdsO0= +github.com/adam-hanna/arrayOperations v0.2.5/go.mod h1:PhqKQzzPMRjFcC4Heh+kxha3nMvJ6lQNKuVEgoyimgU= github.com/cornelk/hashmap v1.0.0 h1:jNHWycAM10SO5Ig76HppMQ69jnbqaziRpqVTNvAxdJQ= github.com/cornelk/hashmap v1.0.0/go.mod h1:8wbysTUDnwJGrPZ1Iwsou3m+An6sldFrJItjRhfegCw= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= @@ -12,6 +14,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/dchest/siphash v1.1.0/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= github.com/dchest/siphash v1.2.1 h1:4cLinnzVJDKxTCl9B01807Yiy+W7ZzVHj/KIroQRvT4= github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= +github.com/google/addlicense v0.0.0-20190907113143-be125746c2c4 h1:Bptr91tgP3H4/tg/69DYMrievvj8AgXXr5ktPmm+p38= +github.com/google/addlicense v0.0.0-20190907113143-be125746c2c4/go.mod h1:QtPG26W17m+OIQgE6gQ24gC1M6pUaMBAbFrTIDtwG/E= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jolestar/go-commons-pool v2.0.0+incompatible h1:uHn5uRKsLLQSf9f1J5QPY2xREWx/YH+e4bIIXcAuAaE= diff --git a/index.go b/index.go index 6f22a73..67784b0 100644 --- a/index.go +++ b/index.go @@ -1,11 +1,30 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gogm import ( "errors" "fmt" + "github.com/adam-hanna/arrayOperations" "github.com/cornelk/hashmap" dsl "github.com/mindstand/go-cypherdsl" - "github.com/mindstand/gogm/util" driver "github.com/mindstand/golang-neo4j-bolt-driver" ) @@ -279,14 +298,14 @@ func verifyAllIndexesAndConstraints(mappedTypes *hashmap.HashMap) error { } //verify from there - delta, found := util.Difference(foundIndexes, indexes) + delta, found := arrayOperations.Difference(foundIndexes, indexes) if !found { return fmt.Errorf("found differences in remote vs ogm for found indexes, %v", delta) } log.Debug(delta) - delta, found = util.Difference(foundConstraints, constraints) + delta, found = arrayOperations.Difference(foundConstraints, constraints) if !found { return fmt.Errorf("found differences in remote vs ogm for found constraints, %v", delta) } diff --git a/index_test.go b/index_test.go index 12b5c2a..b4f9461 100644 --- a/index_test.go +++ b/index_test.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gogm import ( diff --git a/integration_test.go b/integration_test.go index 1a82830..2c8e9f5 100644 --- a/integration_test.go +++ b/integration_test.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gogm import ( diff --git a/interface.go b/interface.go index b0ee451..98f0f22 100644 --- a/interface.go +++ b/interface.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gogm import ( @@ -5,13 +24,20 @@ import ( "reflect" ) +// IEdge specifies required functions for special edge nodes type IEdge interface { + // GetStartNode gets start node of edge GetStartNode() interface{} + // GetStartNodeType gets reflect type of start node GetStartNodeType() reflect.Type + // SetStartNode sets start node of edge SetStartNode(v interface{}) error + // GetEndNode gets end node of edge GetEndNode() interface{} + // GetEndNodeType gets reflect type of end node GetEndNodeType() reflect.Type + // SetEndNode sets end node of edge SetEndNode(v interface{}) error } @@ -70,12 +96,18 @@ type ISession interface { //delete everything, this will literally delete everything PurgeDatabase() error + // closes session Close() error } +// ITransaction specifies functions for Neo4j ACID transactions type ITransaction interface { + // Begin begins transaction Begin() error + // Rollback rolls back transaction Rollback() error + // RollbackWithError wraps original error into rollback error if there is one RollbackWithError(err error) error + // Commit commits transaction Commit() error } diff --git a/load_strategy.go b/load_strategy.go index 06cfa3d..95b4d94 100644 --- a/load_strategy.go +++ b/load_strategy.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gogm import ( @@ -6,13 +25,17 @@ import ( dsl "github.com/mindstand/go-cypherdsl" ) +// Specifies query based load strategy type LoadStrategy int const ( + // PathLoadStrategy uses cypher path PATH_LOAD_STRATEGY LoadStrategy = iota + // SchemaLoadStrategy generates queries specifically from generated schema SCHEMA_LOAD_STRATEGY ) +// PathLoadStrategyMany loads many using path strategy func PathLoadStrategyMany(variable, label string, depth int, additionalConstraints dsl.ConditionOperator) (dsl.Cypher, error) { if variable == "" { return nil, errors.New("variable name cannot be empty") @@ -46,6 +69,7 @@ func PathLoadStrategyMany(variable, label string, depth int, additionalConstrain return builder.Return(false, dsl.ReturnPart{Name: "p"}), nil } +// PathLoadStrategyOne loads one object using path strategy func PathLoadStrategyOne(variable, label string, depth int, additionalConstraints dsl.ConditionOperator) (dsl.Cypher, error) { if variable == "" { return nil, errors.New("variable name cannot be empty") @@ -91,6 +115,7 @@ func PathLoadStrategyOne(variable, label string, depth int, additionalConstraint return builder.Return(false, dsl.ReturnPart{Name: "p"}), nil } +// PathLoadStrategyEdgeConstraint is similar to load many, but requires that it is related to another node via some edge func PathLoadStrategyEdgeConstraint(startVariable, startLabel, endLabel, endTargetField string, minJumps, maxJumps, depth int, additionalConstraints dsl.ConditionOperator) (dsl.Cypher, error) { if startVariable == "" { return nil, errors.New("variable name cannot be empty") diff --git a/load_strategy_test.go b/load_strategy_test.go index 8b79b3c..5c718d6 100644 --- a/load_strategy_test.go +++ b/load_strategy_test.go @@ -1 +1,20 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gogm diff --git a/mocks/ISession.go b/mocks/ISession.go index ddcc54c..2bf065c 100644 --- a/mocks/ISession.go +++ b/mocks/ISession.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + // Code generated by mockery v1.0.0. DO NOT EDIT. package mocks diff --git a/model.go b/model.go index d944da3..92fe84d 100644 --- a/model.go +++ b/model.go @@ -1,14 +1,25 @@ -package gogm - -type Vertex struct { - Id string `json:"-" gogm:"name=id"` - UUID string `json:"uuid" gogm:"pk;name=uuid"` -} +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -type Edge struct { - Id string `json:"-" gogm:"name=id"` -} +package gogm +// specifies how edges are loaded type neoEdgeConfig struct { Id int64 diff --git a/pagination.go b/pagination.go index d4ef18a..74beef5 100644 --- a/pagination.go +++ b/pagination.go @@ -1,12 +1,37 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gogm import "errors" +// pagination configuration type Pagination struct { + // specifies which page number to load PageNumber int + // limits how many records per page LimitPerPage int + // specifies variable to order by OrderByVarName string + // specifies field to order by on OrderByField string + // specifies whether orderby is desc or asc OrderByDesc bool } diff --git a/save.go b/save.go index e36b068..bd62ceb 100644 --- a/save.go +++ b/save.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gogm import ( @@ -9,23 +28,33 @@ import ( "sync" ) +// maximum supported depth const maxSaveDepth = 10 const defaultSaveDepth = 1 +// nodeCreateConf holds configuration for creating new nodes type nodeCreateConf struct { + // params to save Params map[string]interface{} + // type to save by Type reflect.Type + // whether the node is new or not IsNew bool } +// relCreateConf holds configuration for nodes to link together type relCreateConf struct { + // start uuid of relationship StartNodeUUID string + // end uuid of relationship EndNodeUUID string + // any data to store in edge Params map[string]interface{} + // holds direction of the edge Direction dsl.Direction } -//todo optimize +// saves target node and connected node to specified depth func saveDepth(sess *driver.BoltConn, obj interface{}, depth int) error { if sess == nil { return errors.New("session can not be nil") @@ -150,6 +179,7 @@ func saveDepth(sess *driver.BoltConn, obj interface{}, depth int) error { } } +// calculates which relationships to delete func calculateDels(oldRels, curRels map[string]map[string]*RelationConfig) map[string][]int64 { if len(oldRels) == 0 { return map[string][]int64{} @@ -197,6 +227,7 @@ func calculateDels(oldRels, curRels map[string]map[string]*RelationConfig) map[s return dels } +// removes relationships between specified nodes func removeRelations(conn *driver.BoltConn, dels map[string][]int64) error { if dels == nil || len(dels) == 0 { return nil @@ -253,6 +284,7 @@ func removeRelations(conn *driver.BoltConn, dels map[string][]int64) error { } } +// creates nodes func createNodes(conn *driver.BoltConn, crNodes map[string]map[string]nodeCreateConf, nodeRef *map[string]*reflect.Value) (map[string]int64, error) { idMap := map[string]int64{} @@ -347,6 +379,7 @@ func createNodes(conn *driver.BoltConn, crNodes map[string]map[string]nodeCreate return idMap, nil } +// relateNodes connects nodes together using edge config func relateNodes(conn *driver.BoltConn, relations map[string][]relCreateConf, ids map[string]int64) error { if relations == nil || len(relations) == 0 { return errors.New("relations can not be nil or empty") @@ -447,6 +480,7 @@ func relateNodes(conn *driver.BoltConn, relations map[string][]relCreateConf, id return nil } +// validates data used by parse struct func parseValidate(currentDepth, maxDepth int, current *reflect.Value, nodesPtr *map[string]map[string]nodeCreateConf, relationsPtr *map[string][]relCreateConf) error { if currentDepth > maxDepth { return nil @@ -463,6 +497,7 @@ func parseValidate(currentDepth, maxDepth int, current *reflect.Value, nodesPtr return nil } +// generates load map for updated structs func generateCurRels(parentId string, current *reflect.Value, currentDepth, maxDepth int, curRels *map[string]map[string]*RelationConfig) error { if currentDepth > maxDepth { return nil @@ -478,8 +513,6 @@ func generateCurRels(parentId string, current *reflect.Value, currentDepth, maxD return nil } - //id := reflect.Indirect(*current).FieldByName("UUID").Interface().(int64) - //get the type tString, err := getTypeName(current.Type()) if err != nil { @@ -585,6 +618,7 @@ func generateCurRels(parentId string, current *reflect.Value, currentDepth, maxD return nil } +// parses tree of structs func parseStruct(parentId, edgeLabel string, parentIsStart bool, direction dsl.Direction, edgeParams map[string]interface{}, current *reflect.Value, currentDepth, maxDepth int, nodesPtr *map[string]map[string]nodeCreateConf, relationsPtr *map[string][]relCreateConf, oldRels *map[string]map[string]*RelationConfig, newNodes *[]*string, nodeRef *map[string]*reflect.Value) error { @@ -756,6 +790,7 @@ func parseStruct(parentId, edgeLabel string, parentIsStart bool, direction dsl.D return nil } +// processStruct generates configuration for individual struct for saving func processStruct(fieldConf decoratorConfig, relVal *reflect.Value, id, oldParentId string) (parentId, edgeLabel string, parentIsStart bool, direction dsl.Direction, edgeParams map[string]interface{}, followVal *reflect.Value, followId int64, skip bool, err error) { edgeLabel = fieldConf.Relationship diff --git a/save_test.go b/save_test.go index ba7de88..10e5430 100644 --- a/save_test.go +++ b/save_test.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gogm import ( diff --git a/session.go b/session.go index 871a1d6..15410d5 100644 --- a/session.go +++ b/session.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gogm import ( diff --git a/testing_/linking.go b/testing_/linking.go index 85fb24d..945e8d7 100644 --- a/testing_/linking.go +++ b/testing_/linking.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + // code generated by gogm; DO NOT EDIT package testing_ diff --git a/testing_/linking_test.go b/testing_/linking_test.go index 2afe773..0a1929f 100644 --- a/testing_/linking_test.go +++ b/testing_/linking_test.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package testing_ import ( diff --git a/testing_/test_edge.go b/testing_/test_edge.go index 8c13649..97e49fb 100644 --- a/testing_/test_edge.go +++ b/testing_/test_edge.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package testing_ import ( diff --git a/testing_/test_obj.go b/testing_/test_obj.go index 6335e41..4106462 100644 --- a/testing_/test_obj.go +++ b/testing_/test_obj.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package testing_ import "github.com/mindstand/gogm" diff --git a/testing_/test_obj2.go b/testing_/test_obj2.go index 2d6df36..792add4 100644 --- a/testing_/test_obj2.go +++ b/testing_/test_obj2.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package testing_ import "github.com/mindstand/gogm" diff --git a/testing_/test_omit.go b/testing_/test_omit.go index 098482d..3b5892e 100644 --- a/testing_/test_omit.go +++ b/testing_/test_omit.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package testing_ type OrdinaryNonGogmStruct struct { diff --git a/util.go b/util.go index e062412..63225ee 100644 --- a/util.go +++ b/util.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gogm import ( @@ -11,6 +30,7 @@ import ( "time" ) +// checks if integer is in slice func int64SliceContains(s []int64, e int64) bool { for _, a := range s { if a == e { @@ -20,6 +40,7 @@ func int64SliceContains(s []int64, e int64) bool { return false } +// checks if string is in slice func stringSliceContains(s []string, e string) bool { for _, a := range s { if a == e { @@ -29,6 +50,7 @@ func stringSliceContains(s []string, e string) bool { return false } +// sets uuid for stuct if uuid field is empty func setUuidIfNeeded(val *reflect.Value, fieldName string) (bool, string, error) { if val == nil { return false, "", errors.New("value can not be nil") @@ -49,6 +71,7 @@ func setUuidIfNeeded(val *reflect.Value, fieldName string) (bool, string, error) return true, newUuid, nil } +// gets the type name from reflect type func getTypeName(val reflect.Type) (string, error) { if val.Kind() == reflect.Ptr { val = val.Elem() @@ -68,6 +91,7 @@ func getTypeName(val reflect.Type) (string, error) { } } +// converts struct fields to map that cypher can use func toCypherParamsMap(val reflect.Value, config structDecoratorConfig) (map[string]interface{}, error) { var err error defer func() { diff --git a/util/arrayOperations.go b/util/arrayOperations.go deleted file mode 100644 index 409e0eb..0000000 --- a/util/arrayOperations.go +++ /dev/null @@ -1,716 +0,0 @@ -package util - -import ( - "reflect" -) - -// Distinct returns the unique vals of a slice -// -// [1, 1, 2, 3] >> [1, 2, 3] -func Distinct(arr interface{}) (reflect.Value, bool) { - // create a slice from our input interface - slice, ok := takeArg(arr, reflect.Slice) - if !ok { - return reflect.Value{}, ok - } - - // put the values of our slice into a map - // the key's of the map will be the slice's unique values - c := slice.Len() - m := make(map[interface{}]bool) - for i := 0; i < c; i++ { - m[slice.Index(i).Interface()] = true - } - mapLen := len(m) - - // create the output slice and populate it with the map's keys - out := reflect.MakeSlice(reflect.TypeOf(arr), mapLen, mapLen) - i := 0 - for k := range m { - v := reflect.ValueOf(k) - o := out.Index(i) - o.Set(v) - i++ - } - - return out, ok -} - -// Intersect returns a slice of values that are present in all of the input slices -// -// [1, 1, 3, 4, 5, 6] & [2, 3, 6] >> [3, 6] -// -// [1, 1, 3, 4, 5, 6] >> [1, 3, 4, 5, 6] -func Intersect(arrs ...interface{}) (reflect.Value, bool) { - // create a map to count all the instances of the slice elems - arrLength := len(arrs) - var kind reflect.Kind - var kindHasBeenSet bool - - tempMap := make(map[interface{}]int) - for _, arg := range arrs { - tempArr, ok := Distinct(arg) - if !ok { - return reflect.Value{}, ok - } - - // check to be sure the type hasn't changed - if kindHasBeenSet && tempArr.Len() > 0 && tempArr.Index(0).Kind() != kind { - return reflect.Value{}, false - } - if tempArr.Len() > 0 { - kindHasBeenSet = true - kind = tempArr.Index(0).Kind() - } - - c := tempArr.Len() - for idx := 0; idx < c; idx++ { - // how many times have we encountered this elem? - if _, ok := tempMap[tempArr.Index(idx).Interface()]; ok { - tempMap[tempArr.Index(idx).Interface()]++ - } else { - tempMap[tempArr.Index(idx).Interface()] = 1 - } - } - } - - // find the keys equal to the length of the input args - numElems := 0 - for _, v := range tempMap { - if v == arrLength { - numElems++ - } - } - out := reflect.MakeSlice(reflect.TypeOf(arrs[0]), numElems, numElems) - i := 0 - for key, val := range tempMap { - if val == arrLength { - v := reflect.ValueOf(key) - o := out.Index(i) - o.Set(v) - i++ - } - } - - return out, true -} - -// Union returns a slice that contains the unique values of all the input slices -// -// [1, 2, 2, 4, 6] & [2, 4, 5] >> [1, 2, 4, 5, 6] -// -// [1, 1, 3, 4, 5, 6] >> [1, 3, 4, 5, 6] -func Union(arrs ...interface{}) (reflect.Value, bool) { - // create a temporary map to hold the contents of the arrays - tempMap := make(map[interface{}]uint8) - var kind reflect.Kind - var kindHasBeenSet bool - - // write the contents of the arrays as keys to the map. The map values don't matter - for _, arg := range arrs { - tempArr, ok := Distinct(arg) - if !ok { - return reflect.Value{}, ok - } - - // check to be sure the type hasn't changed - if kindHasBeenSet && tempArr.Len() > 0 && tempArr.Index(0).Kind() != kind { - return reflect.Value{}, false - } - if tempArr.Len() > 0 { - kindHasBeenSet = true - kind = tempArr.Index(0).Kind() - } - - c := tempArr.Len() - for idx := 0; idx < c; idx++ { - tempMap[tempArr.Index(idx).Interface()] = 0 - } - } - - // the map keys are now unique instances of all of the array contents - mapLen := len(tempMap) - out := reflect.MakeSlice(reflect.TypeOf(arrs[0]), mapLen, mapLen) - i := 0 - for key := range tempMap { - v := reflect.ValueOf(key) - o := out.Index(i) - o.Set(v) - i++ - } - - return out, true -} - -// Difference returns a slice of values that are only present in one of the input slices -// -// [1, 2, 2, 4, 6] & [2, 4, 5] >> [1, 5, 6] -// -// [1, 1, 3, 4, 5, 6] >> [1, 3, 4, 5, 6] -func Difference(arrs ...interface{}) (reflect.Value, bool) { - // create a temporary map to hold the contents of the arrays - tempMap := make(map[interface{}]int) - var kind reflect.Kind - var kindHasBeenSet bool - - for _, arg := range arrs { - tempArr, ok := Distinct(arg) - if !ok { - return reflect.Value{}, ok - } - - // check to be sure the type hasn't changed - if kindHasBeenSet && tempArr.Len() > 0 && tempArr.Index(0).Kind() != kind { - return reflect.Value{}, false - } - if tempArr.Len() > 0 { - kindHasBeenSet = true - kind = tempArr.Index(0).Kind() - } - - c := tempArr.Len() - for idx := 0; idx < c; idx++ { - // how many times have we encountered this elem? - if _, ok := tempMap[tempArr.Index(idx).Interface()]; ok { - tempMap[tempArr.Index(idx).Interface()]++ - } else { - tempMap[tempArr.Index(idx).Interface()] = 1 - } - } - } - - // write the final val of the diffMap to an array and return - numElems := 0 - for _, v := range tempMap { - if v == 1 { - numElems++ - } - } - out := reflect.MakeSlice(reflect.TypeOf(arrs[0]), numElems, numElems) - i := 0 - for key, val := range tempMap { - if val == 1 { - v := reflect.ValueOf(key) - o := out.Index(i) - o.Set(v) - i++ - } - } - - return out, true -} - -func takeArg(arg interface{}, kind reflect.Kind) (val reflect.Value, ok bool) { - val = reflect.ValueOf(arg) - if val.Kind() == kind { - ok = true - } - return -} - -/* *************************************************************** -* -* THE SECTIONS BELOW ARE DEPRECATED -* -/* *************************************************************** */ - -/* *************************************************************** -* -* THIS SECTION IS FOR STRINGS -* -/* *************************************************************** */ - -// IntersectString finds the intersection of two arrays. -// -// Deprecated: use Intersect instead. -func IntersectString(args ...[]string) []string { - // create a map to count all the instances of the strings - arrLength := len(args) - tempMap := make(map[string]int) - for _, arg := range args { - tempArr := DistinctString(arg) - for idx := range tempArr { - // how many times have we encountered this elem? - if _, ok := tempMap[tempArr[idx]]; ok { - tempMap[tempArr[idx]]++ - } else { - tempMap[tempArr[idx]] = 1 - } - } - } - - // find the keys equal to the length of the input args - tempArray := make([]string, 0) - for key, val := range tempMap { - if val == arrLength { - tempArray = append(tempArray, key) - } - } - - return tempArray -} - -// IntersectStringArr finds the intersection of two arrays using a multidimensional array as inputs -// -// Deprecated: use Intersect instead. -func IntersectStringArr(arr [][]string) []string { - // create a map to count all the instances of the strings - arrLength := len(arr) - tempMap := make(map[string]int) - for idx1 := range arr { - tempArr := DistinctString(arr[idx1]) - for idx2 := range tempArr { - // how many times have we encountered this elem? - if _, ok := tempMap[tempArr[idx2]]; ok { - tempMap[tempArr[idx2]]++ - } else { - tempMap[tempArr[idx2]] = 1 - } - } - } - - // find the keys equal to the length of the input args - tempArray := make([]string, 0) - for key, val := range tempMap { - if val == arrLength { - tempArray = append(tempArray, key) - } - } - - return tempArray -} - -// UnionString finds the union of two arrays. -// -// Deprecated: use Union instead. -func UnionString(args ...[]string) []string { - // create a temporary map to hold the contents of the arrays - tempMap := make(map[string]uint8) - - // write the contents of the arrays as keys to the map. The map values don't matter - for _, arg := range args { - for idx := range arg { - tempMap[arg[idx]] = 0 - } - } - - // the map keys are now unique instances of all of the array contents - tempArray := make([]string, 0) - for key := range tempMap { - tempArray = append(tempArray, key) - } - - return tempArray -} - -// UnionStringArr finds the union of two arrays using a multidimensional array as inputs -// -// Deprecated: use Union instead. -func UnionStringArr(arr [][]string) []string { - // create a temporary map to hold the contents of the arrays - tempMap := make(map[string]uint8) - - // write the contents of the arrays as keys to the map. The map values don't matter - for idx1 := range arr { - for idx2 := range arr[idx1] { - tempMap[arr[idx1][idx2]] = 0 - } - } - - // the map keys are now unique instances of all of the array contents - tempArray := make([]string, 0) - for key := range tempMap { - tempArray = append(tempArray, key) - } - - return tempArray -} - -// DifferenceString finds the difference of two arrays. -// -// Deprecated: use Difference instead. -func DifferenceString(args ...[]string) []string { - // create a temporary map to hold the contents of the arrays - tempMap := make(map[string]int) - for _, arg := range args { - tempArr := DistinctString(arg) - for idx := range tempArr { - // how many times have we encountered this elem? - if _, ok := tempMap[tempArr[idx]]; ok { - tempMap[tempArr[idx]]++ - } else { - tempMap[tempArr[idx]] = 1 - } - } - } - - // write the final val of the diffMap to an array and return - tempArray := make([]string, 0) - for key, val := range tempMap { - if val == 1 { - tempArray = append(tempArray, key) - } - } - - return tempArray -} - -// DifferenceStringArr finds the difference of two arrays using a multidimensional array as inputs -// -// Deprecated: use Difference instead. -func DifferenceStringArr(arr [][]string) []string { - // create a temporary map to hold the contents of the arrays - tempMap := make(map[string]int) - for idx1 := range arr { - tempArr := DistinctString(arr[idx1]) - for idx2 := range tempArr { - // how many times have we encountered this elem? - if _, ok := tempMap[tempArr[idx2]]; ok { - tempMap[tempArr[idx2]]++ - } else { - tempMap[tempArr[idx2]] = 1 - } - } - } - - // write the final val of the diffMap to an array and return - tempArray := make([]string, 0) - for key, val := range tempMap { - if val == 1 { - tempArray = append(tempArray, key) - } - } - - return tempArray -} - -// DistinctString removes duplicate values from one array. -// -// Deprecated: use Distinct instead. -func DistinctString(arg []string) []string { - tempMap := make(map[string]uint8) - - for idx := range arg { - tempMap[arg[idx]] = 0 - } - - tempArray := make([]string, 0) - for key := range tempMap { - tempArray = append(tempArray, key) - } - return tempArray -} - -/* *************************************************************** -* -* THIS SECTION IS FOR uint64's -* -/* *************************************************************** */ - -// IntersectUint64 finds the intersection of two arrays. -// -// Deprecated: use Intersect instead. -func IntersectUint64(args ...[]uint64) []uint64 { - // create a map to count all the instances of the strings - arrLength := len(args) - tempMap := make(map[uint64]int) - for _, arg := range args { - tempArr := DistinctUint64(arg) - for idx := range tempArr { - // how many times have we encountered this elem? - if _, ok := tempMap[tempArr[idx]]; ok { - tempMap[tempArr[idx]]++ - } else { - tempMap[tempArr[idx]] = 1 - } - } - } - - // find the keys equal to the length of the input args - tempArray := make([]uint64, 0) - for key, val := range tempMap { - if val == arrLength { - tempArray = append(tempArray, key) - } - } - - return tempArray -} - -// DistinctIntersectUint64 finds the intersection of two arrays of distinct vals. -// -// Deprecated: use Intersect instead. -func DistinctIntersectUint64(args ...[]uint64) []uint64 { - // create a map to count all the instances of the strings - arrLength := len(args) - tempMap := make(map[uint64]int) - for _, arg := range args { - for idx := range arg { - // how many times have we encountered this elem? - if _, ok := tempMap[arg[idx]]; ok { - tempMap[arg[idx]]++ - } else { - tempMap[arg[idx]] = 1 - } - } - } - - // find the keys equal to the length of the input args - tempArray := make([]uint64, 0) - for key, val := range tempMap { - if val == arrLength { - tempArray = append(tempArray, key) - } - } - - return tempArray -} - -func sortedIntersectUintHelper(a1 []uint64, a2 []uint64) []uint64 { - intersection := make([]uint64, 0) - n1 := len(a1) - n2 := len(a2) - i := 0 - j := 0 - for i < n1 && j < n2 { - switch { - case a1[i] > a2[j]: - j++ - case a2[j] > a1[i]: - i++ - default: - intersection = append(intersection, a1[i]) - i++ - j++ - } - } - return intersection -} - -// SortedIntersectUint64 finds the intersection of two sorted arrays. -// -// Deprecated: use Intersect instead. -func SortedIntersectUint64(args ...[]uint64) []uint64 { - // create an array to hold the intersection and write the first array to it - tempIntersection := args[0] - argsLen := len(args) - - for k := 1; k < argsLen; k++ { - // do we have any intersections? - switch len(tempIntersection) { - case 0: - // nope! Give them an empty array! - return tempIntersection - - default: - // yup, keep chugging - tempIntersection = sortedIntersectUintHelper(tempIntersection, args[k]) - } - } - - return tempIntersection -} - -// IntersectUint64Arr finds the intersection of two arrays using a multidimensional array as inputs -// -// Deprecated: use Intersect instead. -func IntersectUint64Arr(arr [][]uint64) []uint64 { - // create a map to count all the instances of the strings - arrLength := len(arr) - tempMap := make(map[uint64]int) - for idx1 := range arr { - tempArr := DistinctUint64(arr[idx1]) - for idx2 := range tempArr { - // how many times have we encountered this elem? - if _, ok := tempMap[tempArr[idx2]]; ok { - tempMap[tempArr[idx2]]++ - } else { - tempMap[tempArr[idx2]] = 1 - } - } - } - - // find the keys equal to the length of the input args - tempArray := make([]uint64, 0) - for key, val := range tempMap { - if val == arrLength { - tempArray = append(tempArray, key) - } - } - - return tempArray -} - -// SortedIntersectUint64Arr finds the intersection of two arrays using a multidimensional array as inputs -// -// Deprecated: use Intersect instead. -func SortedIntersectUint64Arr(arr [][]uint64) []uint64 { - // create an array to hold the intersection and write the first array to it - tempIntersection := arr[0] - argsLen := len(arr) - - for k := 1; k < argsLen; k++ { - // do we have any intersections? - switch len(tempIntersection) { - case 0: - // nope! Give them an empty array! - return tempIntersection - - default: - // yup, keep chugging - tempIntersection = sortedIntersectUintHelper(tempIntersection, arr[k]) - } - } - - return tempIntersection -} - -// DistinctIntersectUint64Arr finds the intersection of two distinct arrays using a multidimensional array as inputs -// -// Deprecated: use Distinct instead. -func DistinctIntersectUint64Arr(arr [][]uint64) []uint64 { - // create a map to count all the instances of the strings - arrLength := len(arr) - tempMap := make(map[uint64]int) - for idx1 := range arr { - for idx2 := range arr[idx1] { - // how many times have we encountered this elem? - if _, ok := tempMap[arr[idx1][idx2]]; ok { - tempMap[arr[idx1][idx2]]++ - } else { - tempMap[arr[idx1][idx2]] = 1 - } - } - } - - // find the keys equal to the length of the input args - tempArray := make([]uint64, 0) - for key, val := range tempMap { - if val == arrLength { - tempArray = append(tempArray, key) - } - } - - return tempArray -} - -// UnionUint64 finds the union of two arrays. -// -// Deprecated: use Union instead. -func UnionUint64(args ...[]uint64) []uint64 { - // create a temporary map to hold the contents of the arrays - tempMap := make(map[uint64]uint8) - - // write the contents of the arrays as keys to the map. The map values don't matter - for _, arg := range args { - for idx := range arg { - tempMap[arg[idx]] = 0 - } - } - - // the map keys are now unique instances of all of the array contents - tempArray := make([]uint64, 0) - for key := range tempMap { - tempArray = append(tempArray, key) - } - - return tempArray -} - -// UnionUint64Arr finds the union of two arrays using a multidimensional array as inputs -// -// Deprecated: use Union instead. -func UnionUint64Arr(arr [][]uint64) []uint64 { - // create a temporary map to hold the contents of the arrays - tempMap := make(map[uint64]uint8) - - // write the contents of the arrays as keys to the map. The map values don't matter - for idx1 := range arr { - for idx2 := range arr[idx1] { - tempMap[arr[idx1][idx2]] = 0 - } - } - - // the map keys are now unique instances of all of the array contents - tempArray := make([]uint64, 0) - for key := range tempMap { - tempArray = append(tempArray, key) - } - - return tempArray -} - -// DifferenceUint64 finds the difference of two arrays. -// -// Deprecated: use Difference instead. -func DifferenceUint64(args ...[]uint64) []uint64 { - // create a temporary map to hold the contents of the arrays - tempMap := make(map[uint64]int) - for _, arg := range args { - tempArr := DistinctUint64(arg) - for idx := range tempArr { - // how many times have we encountered this elem? - if _, ok := tempMap[tempArr[idx]]; ok { - tempMap[tempArr[idx]]++ - } else { - tempMap[tempArr[idx]] = 1 - } - } - } - - // write the final val of the diffMap to an array and return - tempArray := make([]uint64, 0) - for key, val := range tempMap { - if val == 1 { - tempArray = append(tempArray, key) - } - } - - return tempArray -} - -// DifferenceUint64Arr finds the difference of two arrays using a multidimensional array as inputs. -// -// Deprecated: use Difference instead. -func DifferenceUint64Arr(arr [][]uint64) []uint64 { - // create a temporary map to hold the contents of the arrays - tempMap := make(map[uint64]int) - for idx1 := range arr { - tempArr := DistinctUint64(arr[idx1]) - for idx2 := range tempArr { - // how many times have we encountered this elem? - if _, ok := tempMap[tempArr[idx2]]; ok { - tempMap[tempArr[idx2]]++ - } else { - tempMap[tempArr[idx2]] = 1 - } - } - } - - // write the final val of the diffMap to an array and return - tempArray := make([]uint64, 0) - for key, val := range tempMap { - if val == 1 { - tempArray = append(tempArray, key) - } - } - - return tempArray -} - -// DistinctUint64 removes duplicate values from one array. -// -// Deprecated: use Distinct instead. -func DistinctUint64(arg []uint64) []uint64 { - tempMap := make(map[uint64]uint8) - - for idx := range arg { - tempMap[arg[idx]] = 0 - } - - tempArray := make([]uint64, 0) - for key := range tempMap { - tempArray = append(tempArray, key) - } - return tempArray -} diff --git a/util/arrayOperations_test.go b/util/arrayOperations_test.go deleted file mode 100644 index fea8f4a..0000000 --- a/util/arrayOperations_test.go +++ /dev/null @@ -1,605 +0,0 @@ -package util - -import ( - "fmt" - "reflect" - "testing" -) - -var stringArr1 = []string{"a", "a", "b", "d"} -var stringArr2 = []string{"b", "c", "e"} -var intArr1 = []uint64{1, 1, 2, 4} -var intArr2 = []uint64{2, 3, 5} -var tempInterface interface{} - -func TestDistinct(t *testing.T) { - var myTests = []struct { - input interface{} - pass bool - expected interface{} - }{ - {stringArr1, true, []string{"a", "b", "d"}}, - {stringArr2, true, []string{"b", "c", "e"}}, - {intArr1, true, []uint64{1, 2, 4}}, - {intArr2, true, []uint64{2, 3, 5}}, - {[]int{}, true, []int{}}, - } - - for _, tt := range myTests { - actual, ok := Distinct(tt.input) - - if tt.pass && ok && !testEq(tt.expected, actual) { - t.Errorf("expected: %v, received: %v", tt.expected, actual) - } else if !tt.pass && ok { - t.Errorf("expected fail but received: %v, ok: %v", actual, ok) - } - } -} - -func TestIntersect(t *testing.T) { - var myTests = []struct { - input1 interface{} - input2 interface{} - pass bool - expected interface{} - }{ - {stringArr1, stringArr2, true, []string{"b"}}, - {intArr1, intArr2, true, []uint64{2}}, - {stringArr1, intArr1, false, tempInterface}, - {[]string{}, []string{"1"}, true, []string{}}, - } - - for _, tt := range myTests { - actual, ok := Intersect(tt.input1, tt.input2) - - if tt.pass && ok && !testEq(tt.expected, actual) { - t.Errorf("expected: %v, received: %v", tt.expected, actual) - } else if !tt.pass && ok { - t.Errorf("expected fail but received: %v, ok: %v", actual, ok) - } - } -} - -func TestUnion(t *testing.T) { - var myTests = []struct { - input1 interface{} - input2 interface{} - pass bool - expected interface{} - }{ - {stringArr1, stringArr2, true, []string{"a", "b", "c", "d", "e"}}, - {intArr1, intArr2, true, []uint64{1, 2, 3, 4, 5}}, - {stringArr1, intArr1, false, tempInterface}, - {[]string{}, []string{"1"}, true, []string{"1"}}, - } - - for _, tt := range myTests { - actual, ok := Union(tt.input1, tt.input2) - - if tt.pass && ok && !testEq(tt.expected, actual) { - t.Errorf("expected: %v, received: %v", tt.expected, actual) - } else if !tt.pass && ok { - t.Errorf("expected fail but received: %v, ok: %v", actual, ok) - } - } -} - -func TestDifference(t *testing.T) { - var myTests = []struct { - input1 interface{} - input2 interface{} - pass bool - expected interface{} - }{ - {stringArr1, stringArr2, true, []string{"a", "c", "d", "e"}}, - {intArr1, intArr2, true, []uint64{1, 3, 4, 5}}, - {stringArr1, intArr1, false, tempInterface}, - {[]string{}, []string{"1"}, true, []string{"1"}}, - } - - for _, tt := range myTests { - actual, ok := Difference(tt.input1, tt.input2) - - if tt.pass && ok && !testEq(tt.expected, actual) { - t.Errorf("expected: %v, received: %v", tt.expected, actual) - } else if !tt.pass && ok { - t.Errorf("expected fail but received: %v, ok: %v", actual, ok) - } - } -} - -func TestIntersectString(t *testing.T) { - var myTests = []struct { - input1 []string - input2 []string - expected []string - }{ - {stringArr1, stringArr2, []string{"b"}}, - } - - for _, tt := range myTests { - actual := IntersectString(tt.input1, tt.input2) - - if !testString(tt.expected, actual) { - t.Errorf("expected: %v, received: %v", tt.expected, actual) - } - } -} - -func TestIntersectStringArr(t *testing.T) { - var myTests = []struct { - input [][]string - expected []string - }{ - {[][]string{stringArr1, stringArr2}, []string{"b"}}, - } - - for _, tt := range myTests { - actual := IntersectStringArr(tt.input) - - if !testString(tt.expected, actual) { - t.Errorf("expected: %v, received: %v", tt.expected, actual) - } - } -} - -func TestUnionString(t *testing.T) { - var myTests = []struct { - input1 []string - input2 []string - expected []string - }{ - {stringArr1, stringArr2, []string{"a", "b", "c", "d", "e"}}, - } - - for _, tt := range myTests { - actual := UnionString(tt.input1, tt.input2) - - if !testString(tt.expected, actual) { - t.Errorf("expected: %v, received: %v", tt.expected, actual) - } - } -} - -func TestUnionStringArr(t *testing.T) { - var myTests = []struct { - input [][]string - expected []string - }{ - {[][]string{stringArr1, stringArr2}, []string{"a", "b", "c", "d", "e"}}, - } - - for _, tt := range myTests { - actual := UnionStringArr(tt.input) - - if !testString(tt.expected, actual) { - t.Errorf("expected: %v, received: %v", tt.expected, actual) - } - } -} - -func TestDifferenceString(t *testing.T) { - var myTests = []struct { - input1 []string - input2 []string - expected []string - }{ - {stringArr1, stringArr2, []string{"a", "c", "d", "e"}}, - } - - for _, tt := range myTests { - actual := DifferenceString(tt.input1, tt.input2) - - if !testString(tt.expected, actual) { - t.Errorf("expected: %v, received: %v", tt.expected, actual) - } - } -} - -func TestDifferenceStringArr(t *testing.T) { - var myTests = []struct { - input [][]string - expected []string - }{ - {[][]string{stringArr1, stringArr2}, []string{"a", "c", "d", "e"}}, - } - - for _, tt := range myTests { - actual := DifferenceStringArr(tt.input) - - if !testString(tt.expected, actual) { - t.Errorf("expected: %v, received: %v", tt.expected, actual) - } - } -} - -func TestDistinctString(t *testing.T) { - var myTests = []struct { - input []string - expected []string - }{ - {stringArr1, []string{"a", "b", "d"}}, - {stringArr2, []string{"b", "c", "e"}}, - } - - for _, tt := range myTests { - actual := DistinctString(tt.input) - - if !testString(tt.expected, actual) { - t.Errorf("expected: %v, received: %v", tt.expected, actual) - } - } -} - -///////////// -///////////// - -func TestIntersectUint64(t *testing.T) { - var myTests = []struct { - input1 []uint64 - input2 []uint64 - expected []uint64 - }{ - {intArr1, intArr2, []uint64{2}}, - } - - for _, tt := range myTests { - actual := IntersectUint64(tt.input1, tt.input2) - - if !testuInt64(tt.expected, actual) { - t.Errorf("expected: %v, received: %v", tt.expected, actual) - } - } -} - -func TestDistinctIntersectUint64(t *testing.T) { - var myTests = []struct { - input1 []uint64 - input2 []uint64 - expected []uint64 - }{ - {[]uint64{1, 2, 4}, []uint64{2, 3, 5}, []uint64{2}}, - } - - for _, tt := range myTests { - actual := DistinctIntersectUint64(tt.input1, tt.input2) - - if !testuInt64(tt.expected, actual) { - t.Errorf("expected: %v, received: %v", tt.expected, actual) - } - } -} - -func TestDistinctIntersectUint64Arr(t *testing.T) { - var myTests = []struct { - input [][]uint64 - expected []uint64 - }{ - {[][]uint64{{1, 2, 4}, {2, 3, 5}}, []uint64{2}}, - } - - for _, tt := range myTests { - actual := DistinctIntersectUint64Arr(tt.input) - - if !testuInt64(tt.expected, actual) { - t.Errorf("expected: %v, received: %v", tt.expected, actual) - } - } -} - -func TestIntersectUint64Arr(t *testing.T) { - var myTests = []struct { - input [][]uint64 - expected []uint64 - }{ - {[][]uint64{intArr1, intArr2}, []uint64{2}}, - } - - for _, tt := range myTests { - actual := IntersectUint64Arr(tt.input) - - if !testuInt64(tt.expected, actual) { - t.Errorf("expected: %v, received: %v", tt.expected, actual) - } - } -} - -func TestSortedIntersectUint64(t *testing.T) { - var myTests = []struct { - input1 []uint64 - input2 []uint64 - expected []uint64 - }{ - {[]uint64{1, 2, 4}, []uint64{2, 3, 5}, []uint64{2}}, - } - - for _, tt := range myTests { - actual := SortedIntersectUint64(tt.input1, tt.input2) - - if !testuInt64(tt.expected, actual) { - t.Errorf("expected: %v, received: %v", tt.expected, actual) - } - } -} - -func TestSortedIntersectUint64Arr(t *testing.T) { - var myTests = []struct { - input [][]uint64 - expected []uint64 - }{ - {[][]uint64{{1, 2, 4}, {2, 3, 5}}, []uint64{2}}, - } - - for _, tt := range myTests { - actual := SortedIntersectUint64Arr(tt.input) - - if !testuInt64(tt.expected, actual) { - t.Errorf("expected: %v, received: %v", tt.expected, actual) - } - } -} - -func TestUnionUint64(t *testing.T) { - var myTests = []struct { - input1 []uint64 - input2 []uint64 - expected []uint64 - }{ - {intArr1, intArr2, []uint64{1, 2, 3, 4, 5}}, - } - - for _, tt := range myTests { - actual := UnionUint64(tt.input1, tt.input2) - - if !testuInt64(tt.expected, actual) { - t.Errorf("expected: %v, received: %v", tt.expected, actual) - } - } -} - -func TestUnionUint64Arr(t *testing.T) { - var myTests = []struct { - input [][]uint64 - expected []uint64 - }{ - {[][]uint64{intArr1, intArr2}, []uint64{1, 2, 3, 4, 5}}, - } - - for _, tt := range myTests { - actual := UnionUint64Arr(tt.input) - - if !testuInt64(tt.expected, actual) { - t.Errorf("expected: %v, received: %v", tt.expected, actual) - } - } -} - -func TestDifferenceUint64(t *testing.T) { - var myTests = []struct { - input1 []uint64 - input2 []uint64 - expected []uint64 - }{ - {intArr1, intArr2, []uint64{1, 3, 4, 5}}, - } - - for _, tt := range myTests { - actual := DifferenceUint64(tt.input1, tt.input2) - - if !testuInt64(tt.expected, actual) { - t.Errorf("expected: %v, received: %v", tt.expected, actual) - } - } -} - -func TestDifferenceUint64Arr(t *testing.T) { - var myTests = []struct { - input [][]uint64 - expected []uint64 - }{ - {[][]uint64{intArr1, intArr2}, []uint64{1, 3, 4, 5}}, - } - - for _, tt := range myTests { - actual := DifferenceUint64Arr(tt.input) - - if !testuInt64(tt.expected, actual) { - t.Errorf("expected: %v, received: %v", tt.expected, actual) - } - } -} - -func TestDistinctUint64(t *testing.T) { - var myTests = []struct { - input []uint64 - expected []uint64 - }{ - {intArr1, []uint64{1, 2, 4}}, - {intArr2, []uint64{2, 3, 5}}, - } - - for _, tt := range myTests { - actual := DistinctUint64(tt.input) - - if !testuInt64(tt.expected, actual) { - t.Errorf("expected: %v, received: %v", tt.expected, actual) - } - } -} - -// Examples -func ExampleDistinct() { - var a = []int{1, 1, 2, 3} - - z, ok := Distinct(a) - if !ok { - fmt.Println("Cannot find distinct") - } - - slice, ok := z.Interface().([]int) - if !ok { - fmt.Println("Cannot convert to slice") - } - fmt.Println(slice, reflect.TypeOf(slice)) // [1, 2, 3] []int -} - -func ExampleIntersect() { - var a = []int{1, 1, 2, 3} - var b = []int{2, 4} - - z, ok := Intersect(a, b) - if !ok { - fmt.Println("Cannot find intersect") - } - - slice, ok := z.Interface().([]int) - if !ok { - fmt.Println("Cannot convert to slice") - } - fmt.Println(slice, reflect.TypeOf(slice)) // [2] []int -} - -func ExampleUnion() { - var a = []int{1, 1, 2, 3} - var b = []int{2, 4} - - z, ok := Union(a, b) - if !ok { - fmt.Println("Cannot find union") - } - - slice, ok := z.Interface().([]int) - if !ok { - fmt.Println("Cannot convert to slice") - } - fmt.Println(slice, reflect.TypeOf(slice)) // [1, 2, 3, 4] []int -} - -func ExampleDifference() { - var a = []int{1, 1, 2, 3} - var b = []int{2, 4} - - z, ok := Difference(a, b) - if !ok { - fmt.Println("Cannot find difference") - } - - slice, ok := z.Interface().([]int) - if !ok { - fmt.Println("Cannot convert to slice") - } - fmt.Println(slice, reflect.TypeOf(slice)) // [1, 3] []int -} - -// Thanks! http://stackoverflow.com/a/15312097/3512709 -func testEq(a, b interface{}) bool { - - if a == nil && b == nil { - fmt.Println("Both nil") - return true - } - - if a == nil || b == nil { - fmt.Println("One nil") - return false - } - - aSlice, ok := takeArg(a, reflect.Slice) - if !ok { - fmt.Println("Can't takeArg a") - return ok - } - bSlice, ok := b.(reflect.Value) - if !ok { - fmt.Println("Can't takeArg b") - return ok - } - aLen := aSlice.Len() - bLen := bSlice.Len() - - if aLen != bLen { - fmt.Println("Arr lengths not equal") - return false - } - -OUTER: - for i := 0; i < aLen; i++ { - foundVal := false - for j := 0; j < bLen; j++ { - if aSlice.Index(i).Interface() == bSlice.Index(j).Interface() { - foundVal = true - continue OUTER - } - } - - if !foundVal { - return false - } - } - - return true -} - -func testString(a, b []string) bool { - - if a == nil && b == nil { - return true - } - - if a == nil || b == nil { - return false - } - - if len(a) != len(b) { - return false - } - -OUTER: - for _, aEl := range a { - foundVal := false - for _, bEl := range b { - if aEl == bEl { - foundVal = true - continue OUTER - } - } - - if !foundVal { - return false - } - } - - return true -} - -func testuInt64(a, b []uint64) bool { - - if a == nil && b == nil { - return true - } - - if a == nil || b == nil { - return false - } - - if len(a) != len(b) { - return false - } - -OUTER: - for _, aEl := range a { - foundVal := false - for _, bEl := range b { - if aEl == bEl { - foundVal = true - continue OUTER - } - } - - if !foundVal { - return false - } - } - - return true -} diff --git a/util_test.go b/util_test.go index 6abaeb2..7b77a93 100644 --- a/util_test.go +++ b/util_test.go @@ -1,3 +1,22 @@ +// Copyright (c) 2019 MindStand Technologies, Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package gogm import ( From e58c8b8e09037fab1c16886a4cb86579f7f49f81 Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Thu, 5 Dec 2019 13:43:28 -0500 Subject: [PATCH 25/27] updated readme --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index a9589e1..2cb9408 100644 --- a/README.md +++ b/README.md @@ -198,9 +198,6 @@ Inspiration came from the Java OGM implementation by Neo4j. - TLS Support - Documentation (obviously) -## Credits -- [adam hannah's arrayOperations](https://github.com/adam-hanna/arrayOperations) - ## How you can help - Report Bugs - Fix bugs From 3d63001dbfab20bfd4e0c51189aa3fdc2e4cba40 Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Thu, 5 Dec 2019 13:43:48 -0500 Subject: [PATCH 26/27] gofmt --- cmd/gogmcli/gen/gen.go | 2 +- config.go | 6 ++--- decorator.go | 58 +++++++++++++++++++++--------------------- defaults.go | 6 ++--- pagination.go | 8 +++--- save.go | 10 ++++---- 6 files changed, 45 insertions(+), 45 deletions(-) diff --git a/cmd/gogmcli/gen/gen.go b/cmd/gogmcli/gen/gen.go index 6b465d4..1af11f2 100644 --- a/cmd/gogmcli/gen/gen.go +++ b/cmd/gogmcli/gen/gen.go @@ -274,4 +274,4 @@ func parseDirection(rel *relConf, rels []*relConf, tplRel *tplRelConf, isSpecial } return nil -} \ No newline at end of file +} diff --git a/config.go b/config.go index c0ad1ba..684a23e 100644 --- a/config.go +++ b/config.go @@ -57,7 +57,7 @@ type Config struct { // Host is the neo4j host Host string `yaml:"host" json:"host"` // Port is the neo4j port - Port int `yaml:"port" json:"port"` + Port int `yaml:"port" json:"port"` // IsCluster specifies whether GoGM is connecting to a casual cluster or not IsCluster bool `yaml:"is_cluster" json:"is_cluster"` @@ -92,11 +92,11 @@ type IndexStrategy int const ( // Assert Index ensures that all indices are set and sets them if they are not there - ASSERT_INDEX IndexStrategy = 0 + ASSERT_INDEX IndexStrategy = 0 // Validate Index ensures that all indices are set VALIDATE_INDEX IndexStrategy = 1 // Ignore Index skips the index step of setup - IGNORE_INDEX IndexStrategy = 2 + IGNORE_INDEX IndexStrategy = 2 ) //holds mapped types diff --git a/decorator.go b/decorator.go index 87257ca..3521a9b 100644 --- a/decorator.go +++ b/decorator.go @@ -38,7 +38,7 @@ var timeType = reflect.TypeOf(time.Time{}) const ( // specifies the name in neo4j //requires assignment (if specified) - paramNameField = "name" + paramNameField = "name" // specifies the name of the relationship //requires assignment (if edge field) @@ -46,65 +46,65 @@ const ( //specifies direction, can only be (incoming|outgoing|both|none) //requires assignment (if edge field) - directionField = "direction" + directionField = "direction" //specifies if the field contains time representation - timeField = "time" + timeField = "time" //specifies if the field is to be indexed - indexField = "index" + indexField = "index" //specifies if the field is unique - uniqueField = "unique" + uniqueField = "unique" //specifies is the field is a primary key - primaryKeyField = "pk" + primaryKeyField = "pk" //specifies if the field is map of type `map[string]interface{}` - propertiesField = "properties" + propertiesField = "properties" //specifies if the field is to be ignored - ignoreField = "-" + ignoreField = "-" //specifies deliminator between GoGM tags - deliminator = ";" + deliminator = ";" //assignment operator for GoGM tags - assignmentOperator = "=" + assignmentOperator = "=" ) //decorator config defines configuration of GoGM field type decoratorConfig struct { // holds reflect type for the field - Type reflect.Type `json:"-"` + Type reflect.Type `json:"-"` // holds the name of the field for neo4j - Name string `json:"name"` + Name string `json:"name"` // holds the name of the field in the struct - FieldName string `json:"field_name"` + FieldName string `json:"field_name"` // holds the name of the relationship - Relationship string `json:"relationship"` + Relationship string `json:"relationship"` // holds the direction - Direction dsl.Direction `json:"direction"` + Direction dsl.Direction `json:"direction"` // specifies if field is to be unique - Unique bool `json:"unique"` + Unique bool `json:"unique"` // specifies if field is to be indexed - Index bool `json:"index"` + Index bool `json:"index"` // specifies if field represents many relationship - ManyRelationship bool `json:"many_relationship"` + ManyRelationship bool `json:"many_relationship"` // uses edge specifies if the edge is a special node - UsesEdgeNode bool `json:"uses_edge_node"` + UsesEdgeNode bool `json:"uses_edge_node"` // specifies whether the field is the nodes primary key - PrimaryKey bool `json:"primary_key"` + PrimaryKey bool `json:"primary_key"` // specify if the field holds properties - Properties bool `json:"properties"` + Properties bool `json:"properties"` // specifies if the field contains time value - IsTime bool `json:"is_time"` + IsTime bool `json:"is_time"` // specifies if the field contains a typedef of another type - IsTypeDef bool `json:"is_type_def"` + IsTypeDef bool `json:"is_type_def"` // holds the reflect type of the root type if typedefed - TypedefActual reflect.Type `json:"-"` + TypedefActual reflect.Type `json:"-"` // specifies whether to ignore the field - Ignore bool `json:"ignore"` + Ignore bool `json:"ignore"` } // Equals checks equality of decorator configs @@ -123,13 +123,13 @@ func (d *decoratorConfig) Equals(comp *decoratorConfig) bool { type structDecoratorConfig struct { // Holds fields -> their configurations // field name : decorator configuration - Fields map[string]decoratorConfig `json:"fields"` + Fields map[string]decoratorConfig `json:"fields"` // holds label for the node, maps to struct name - Label string `json:"label"` + Label string `json:"label"` // specifies if the node is a vertex or an edge (if true, its a vertex) - IsVertex bool `json:"is_vertex"` + IsVertex bool `json:"is_vertex"` // holds the reflect type of the struct - Type reflect.Type `json:"-"` + Type reflect.Type `json:"-"` } // Equals checks equality of structDecoratorConfigs diff --git a/defaults.go b/defaults.go index 62079ce..bf5de72 100644 --- a/defaults.go +++ b/defaults.go @@ -24,7 +24,7 @@ const loadMapField = "LoadMap" // BaseNode contains fields that ALL GoGM nodes are required to have type BaseNode struct { // Id is the GraphId that neo4j uses internally - Id int64 `json:"-" gogm:"name=id"` + Id int64 `json:"-" gogm:"name=id"` // UUID is the unique identifier GoGM uses as a primary key UUID string `json:"uuid" gogm:"pk;name=uuid"` @@ -42,13 +42,13 @@ const ( Single RelationType = 0 // Side of relationship can point to 0+ other nodes - Multi RelationType = 1 + Multi RelationType = 1 ) // RelationConfig specifies how relationships are loaded type RelationConfig struct { // stores graph ids - Ids []int64 `json:"-" gomg:"-"` + Ids []int64 `json:"-" gomg:"-"` // specifies relationship type RelationType RelationType `json:"-" gomg:"-"` } diff --git a/pagination.go b/pagination.go index 74beef5..c7619ee 100644 --- a/pagination.go +++ b/pagination.go @@ -24,15 +24,15 @@ import "errors" // pagination configuration type Pagination struct { // specifies which page number to load - PageNumber int + PageNumber int // limits how many records per page - LimitPerPage int + LimitPerPage int // specifies variable to order by OrderByVarName string // specifies field to order by on - OrderByField string + OrderByField string // specifies whether orderby is desc or asc - OrderByDesc bool + OrderByDesc bool } func (p *Pagination) Validate() error { diff --git a/save.go b/save.go index bd62ceb..e4566ac 100644 --- a/save.go +++ b/save.go @@ -37,9 +37,9 @@ type nodeCreateConf struct { // params to save Params map[string]interface{} // type to save by - Type reflect.Type + Type reflect.Type // whether the node is new or not - IsNew bool + IsNew bool } // relCreateConf holds configuration for nodes to link together @@ -47,11 +47,11 @@ type relCreateConf struct { // start uuid of relationship StartNodeUUID string // end uuid of relationship - EndNodeUUID string + EndNodeUUID string // any data to store in edge - Params map[string]interface{} + Params map[string]interface{} // holds direction of the edge - Direction dsl.Direction + Direction dsl.Direction } // saves target node and connected node to specified depth From 82f9750841784ac3ee678c6ee01d04b5103e8ad9 Mon Sep 17 00:00:00 2001 From: Eric Solender Date: Thu, 5 Dec 2019 13:46:36 -0500 Subject: [PATCH 27/27] added badge to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2cb9408..b248993 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/mindstand/gogm)](https://goreportcard.com/report/github.com/mindstand/gogm) [![Actions Status](https://github.com/mindstand/gogm/workflows/Go/badge.svg)](https://github.com/mindstand/gogm/actions) +[![](https://godoc.org/github.com/nathany/looper?status.svg)](http://godoc.org/github.com/mindstand/gogm) # GoGM Golang Object Graph Mapper ```