Skip to content

Commit

Permalink
Merge pull request #82 from mindstand/nikitawootten/issue60
Browse files Browse the repository at this point in the history
Schema Load Strategy
  • Loading branch information
Eric Solender authored Jan 13, 2022
2 parents a21ccd4 + 21b5357 commit a6c8fc2
Show file tree
Hide file tree
Showing 25 changed files with 740 additions and 189 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

name: Go
on: [push, pull_request]
on:
push:
branches:
- master
pull_request: {}
jobs:

build:
name: Build
runs-on: ubuntu-latest
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ certs/
*.out
.idea/
vendor/

.vscode
5 changes: 0 additions & 5 deletions .vscode/settings.json

This file was deleted.

19 changes: 16 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ go get -u github.com/mindstand/gogm/v2
Primary key strategies allow more customization over primary keys. A strategy is provided to gogm on initialization.
<br/>
Built in primary key strategies are:
- gogm.DefaultPrimaryKeyStrategy -- just use the graph id from neo4j as the primary key
- gogm.UUIDPrimaryKeyStrategy -- uuid's as primary keys
- `gogm.DefaultPrimaryKeyStrategy` -- just use the graph id from neo4j as the primary key
- `gogm.UUIDPrimaryKeyStrategy` -- uuid's as primary keys
```go
// Example of the internal UUID strategy
PrimaryKeyStrategy{
Expand All @@ -52,6 +52,17 @@ PrimaryKeyStrategy{
}
```

### Load Strategy
Load strategies allow control over the queries generated by `Load` operations.
Different strategies change the size of the queries sent to the database as well as the amount of work the database has to do.
A load strategy is provided to gomg on initialization.

The defined load strategies are:
- `gogm.PATH_LOAD_STRATEGY` -- Use cypher path queries to generate simple queries for load operations.
- `gogm.SCHEMA_LOAD_STRATEGY` -- Leverage the GoGM schema to generate more complex queries for load operations which results in less work for the database.

Depending on your use case, `PATH_LOAD_STRATEGY` may result in higher latency.

### Struct Configuration
##### <s>text</s> notates deprecation

Expand Down Expand Up @@ -167,6 +178,8 @@ func main() {
EnableLogParams: false,
// enable open tracing. Ensure contexts have spans already. GoGM does not make root spans, only child spans
OpentracingEnabled: false,
// specify the method gogm will use to generate Load queries
LoadStrategy: gogm.PATH_LOAD_STRATEGY // set to SCHEMA_LOAD_STRATEGY for schema-aware queries which may reduce load on the database
}

// register all vertices and edges
Expand Down Expand Up @@ -285,7 +298,7 @@ sess, err := gogm.G().NewSessionV2(gogm.SessionConfig{AccessMode: gogm.AccessMod

## CLI Installation
```
go get -u github.com/mindstand/gogm/v2/cli/gogmcli
go get -u github.com/mindstand/gogm/v2/cmd/gogmcli
```

## CLI Usage
Expand Down
19 changes: 19 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ type Config struct {
EnableLogParams bool `json:"enable_log_properties" yaml:"enable_log_properties" mapstructure:"enable_log_properties"`

OpentracingEnabled bool `json:"opentracing_enabled" yaml:"opentracing_enabled" mapstructure:"opentracing_enabled"`

LoadStrategy LoadStrategy `json:"load_strategy" yaml:"load_strategy" mapstructure:"load_strategy"`
}

func (c *Config) validate() error {
Expand All @@ -93,6 +95,14 @@ func (c *Config) validate() error {
c.TargetDbs = []string{"neo4j"}
}

if err := c.IndexStrategy.validate(); err != nil {
return err
}

if err := c.LoadStrategy.validate(); err != nil {
return err
}

return nil
}

Expand Down Expand Up @@ -126,3 +136,12 @@ const (
// IGNORE_INDEX skips the index step of setup
IGNORE_INDEX IndexStrategy = 2
)

func (is IndexStrategy) validate() error {
switch is {
case ASSERT_INDEX, VALIDATE_INDEX, IGNORE_INDEX:
return nil
default:
return fmt.Errorf("invalid index strategy %d", is)
}
}
63 changes: 41 additions & 22 deletions decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,41 @@ package gogm
import (
"errors"
"fmt"
"github.com/neo4j/neo4j-go-driver/v4/neo4j"
"reflect"
"strings"

"github.com/neo4j/neo4j-go-driver/v4/neo4j"
)

func traverseResultRecordValues(values []interface{}) ([]neo4j.Path, []neo4j.Relationship, []neo4j.Node) {
var paths []neo4j.Path
var strictRels []neo4j.Relationship
var isolatedNodes []neo4j.Node

for _, value := range values {
switch ct := value.(type) {
case neo4j.Path:
paths = append(paths, ct)
case neo4j.Relationship:
strictRels = append(strictRels, ct)
case neo4j.Node:
isolatedNodes = append(isolatedNodes, ct)
case []interface{}:
v, ok := value.([]interface{})
if ok {
p, r, n := traverseResultRecordValues(v)
paths = append(paths, p...)
strictRels = append(strictRels, r...)
isolatedNodes = append(isolatedNodes, n...)
}
default:
continue
}
}

return paths, strictRels, isolatedNodes
}

//decodes raw path response from driver
//example query `match p=(n)-[*0..5]-() return p`
func decode(gogm *Gogm, result neo4j.Result, respObj interface{}) (err error) {
Expand Down Expand Up @@ -61,21 +91,10 @@ func decode(gogm *Gogm, result neo4j.Result, respObj interface{}) (err error) {
var isolatedNodes []neo4j.Node

for result.Next() {
for _, value := range result.Record().Values {
switch ct := value.(type) {
case neo4j.Path:
paths = append(paths, ct)
break
case neo4j.Relationship:
strictRels = append(strictRels, ct)
break
case neo4j.Node:
isolatedNodes = append(isolatedNodes, ct)
break
default:
continue
}
}
p, r, n := traverseResultRecordValues(result.Record().Values)
paths = append(paths, p...)
strictRels = append(strictRels, r...)
isolatedNodes = append(isolatedNodes, n...)
}

nodeLookup := make(map[int64]*reflect.Value)
Expand All @@ -84,21 +103,21 @@ func decode(gogm *Gogm, result neo4j.Result, respObj interface{}) (err error) {
rels := make(map[int64]*neoEdgeConfig)
labelLookup := map[int64]string{}

if paths != nil && len(paths) != 0 {
if len(paths) != 0 {
err = sortPaths(gogm, paths, &nodeLookup, &rels, &pks, primaryLabel, &relMaps)
if err != nil {
return err
}
}

if isolatedNodes != nil && len(isolatedNodes) != 0 {
if len(isolatedNodes) != 0 {
err = sortIsolatedNodes(gogm, isolatedNodes, &labelLookup, &nodeLookup, &pks, primaryLabel, &relMaps)
if err != nil {
return err
}
}

if strictRels != nil && len(strictRels) != 0 {
if len(strictRels) != 0 {
err = sortStrictRels(strictRels, &labelLookup, &rels)
if err != nil {
return err
Expand Down Expand Up @@ -232,14 +251,14 @@ func decode(gogm *Gogm, result neo4j.Result, respObj interface{}) (err error) {

//can ensure that it implements proper interface if it made it this far
res := val.MethodByName("SetStartNode").Call([]reflect.Value{startCall})
if res == nil || len(res) == 0 {
if len(res) == 0 {
return fmt.Errorf("invalid response from edge callback - %w", err)
} else if !res[0].IsNil() {
return fmt.Errorf("failed call to SetStartNode - %w", res[0].Interface().(error))
}

res = val.MethodByName("SetEndNode").Call([]reflect.Value{endCall})
if res == nil || len(res) == 0 {
if len(res) == 0 {
return fmt.Errorf("invalid response from edge callback - %w", err)
} else if !res[0].IsNil() {
return fmt.Errorf("failed call to SetEndNode - %w", res[0].Interface().(error))
Expand Down Expand Up @@ -359,7 +378,7 @@ func sortIsolatedNodes(gogm *Gogm, isolatedNodes []neo4j.Node, labelLookup *map[
}

//set label map
if _, ok := (*labelLookup)[node.Id]; !ok && len(node.Labels) != 0 && node.Labels[0] == pkLabel {
if _, ok := (*labelLookup)[node.Id]; !ok && len(node.Labels) != 0 { //&& node.Labels[0] == pkLabel {
(*labelLookup)[node.Id] = node.Labels[0]
}
}
Expand Down
Loading

0 comments on commit a6c8fc2

Please sign in to comment.