Golang Object Graph Mapper for Neo4j

Go Report Card Actions Status GoDoc

GoGM Golang Object Graph Mapper v2

go get -u github.com/mindstand/gogm/v2

Features

  • Struct Mapping through the gogm struct decorator
  • Full support for ACID transactions
  • Underlying connection pooling
  • Support for HA Casual Clusters using bolt+routing through the Official Neo4j Go Driver
  • Custom queries in addition to built in functionality
  • Builder pattern cypher queries using MindStand's cypher dsl package
  • CLI to generate link and unlink functions for gogm structs.
  • Multi database support with Neo4j v4

What's new in V2

  • GoGM is an object now! This means you can have multiple instances of GoGM at a time
  • OpenTracing and Context Support
  • Driver has been updated from v1.6 to v4
  • Log interface, so anyone can use the logger of their choice instead of being forced to use logrus
  • Primary Key strategies to use any type of primary key. GoGM is no longer UUID only!

Usage

Primary Key Strategy

Primary key strategies allow more customization over primary keys. A strategy is provided to gogm on initialization.
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
// Example of the internal UUID strategy
PrimaryKeyStrategy{
	// StrategyName is the name to reference the strategy
    StrategyName: "UUID",
    // DBName is the name of the field in the database
    DBName:       "uuid",
    // FieldName is the name of the field in go, this will validate to make sure all pk's use the same field name
    FieldName:    "UUID",
    // Type is the reflect type of the primary key, in this case it's a string but it can be any primitive
    Type:         reflect.TypeOf(""),
    // GenIDFunc defines how new ids are generated, in this case we're using googles uuid library
    GenIDFunc: func() (id interface{}) {
        return uuid.New().String()
    },
}

Struct Configuration

text notates deprecation

Decorators that can be used

  • name= -- used to set the field name that will show up in neo4j.
  • relationship= -- used to set the name of the edge on that field.
  • direction= -- used to specify direction of that edge field.
  • index -- marks field to have an index applied to it.
  • unique -- marks field to have unique constraint.
  • pk= -- marks field as a primary key and specifies which pk strategy to use. Can only have one pk, composite pk's are not supported.
  • properties -- marks that field is using a map. GoGM only supports properties fields of map[string]interface{}, map[string], map[string][] and []
  • - -- marks that field will be ignored by the ogm

Not on relationship member variables

All relationships must be defined as either a pointer to a struct or a slice of struct pointers *SomeStruct or []*SomeStruct

Use ; as delimiter between decorator tags.

Ex.

` in neo4j IgnoreMe bool `gogm="-"` UniqueTypeDef TdString `gogm:"name=unique_type_def"` Relation *SomeOtherStruct `gogm="relationship=SOME_STRUCT;direction=OUTGOING"` ManyRelation []*SomeStruct `gogm="relationship=MANY;direction=INCOMING"` } ">
type TdString string

type MyNeo4jObject struct {
  // provides required node field
  // use gogm.BaseUUIDNode if you want to use UUIDs
  gogm.BaseNode

  Field string `gogm:"name=field"`
  Props map[string]interface{} `gogm:"properties;name=props"` //note that this would show up as `props.` in neo4j
  IgnoreMe bool `gogm="-"`
  UniqueTypeDef TdString `gogm:"name=unique_type_def"`
  Relation *SomeOtherStruct `gogm="relationship=SOME_STRUCT;direction=OUTGOING"`
  ManyRelation []*SomeStruct `gogm="relationship=MANY;direction=INCOMING"`
}

GOGM Usage

package main

import (
	"github.com/mindstand/gogm/v2"
	"time"
)

type tdString string
type tdInt int

//structs for the example (can also be found in decoder_test.go)
type VertexA struct {
	// provides required node fields
	gogm.BaseNode

    TestField         string                `gogm:"name=test_field"`
	TestTypeDefString tdString          `gogm:"name=test_type_def_string"`
	TestTypeDefInt    tdInt             `gogm:"name=test_type_def_int"`
	MapProperty       map[string]string `gogm:"name=map_property;properties"`
	SliceProperty     []string          `gogm:"name=slice_property;properties"`
    SingleA           *VertexB          `gogm:"direction=incoming;relationship=test_rel"`
	ManyA             []*VertexB        `gogm:"direction=incoming;relationship=testm2o"`
	MultiA            []*VertexB        `gogm:"direction=incoming;relationship=multib"`
	SingleSpecA       *EdgeC            `gogm:"direction=outgoing;relationship=special_single"`
	MultiSpecA        []*EdgeC          `gogm:"direction=outgoing;relationship=special_multi"`
}

type VertexB struct {
	// provides required node fields
	gogm.BaseNode

	TestField  string     `gogm:"name=test_field"`
	TestTime   time.Time  `gogm:"name=test_time"`
	Single     *VertexA   `gogm:"direction=outgoing;relationship=test_rel"`
	ManyB      *VertexA   `gogm:"direction=outgoing;relationship=testm2o"`
	Multi      []*VertexA `gogm:"direction=outgoing;relationship=multib"`
	SingleSpec *EdgeC     `gogm:"direction=incoming;relationship=special_single"`
	MultiSpec  []*EdgeC   `gogm:"direction=incoming;relationship=special_multi"`
}

type EdgeC struct {
	// provides required node fields
	gogm.BaseNode

	Start *VertexA
	End   *VertexB
	Test  string `gogm:"name=test"`
}

func main() {
	// define your configuration
	config := gogm.Config{
		Host:                      "0.0.0.0",
		Port:                      7687,
		IsCluster:                 false, //tells it whether or not to use `bolt+routing`
		Username:                  "neo4j",
		Password:                  "password",
		PoolSize:                  50,
		Encrypted:                 false,
		IndexStrategy:             gogm.VALIDATE_INDEX, //other options are ASSERT_INDEX and IGNORE_INDEX
		TargetDbs:                 nil,
		// default logger wraps the go "log" package, implement the Logger interface from gogm to use your own logger
		Logger:             gogm.GetDefaultLogger(),
		// define the log level
		LogLevel:           "DEBUG",
		// enable neo4j go driver to log
		EnableDriverLogs:   false,
		// enable gogm to log params in cypher queries. WARNING THIS IS A SECURITY RISK! Only use this when debugging
		EnableLogParams:    false,
		// enable open tracing. Ensure contexts have spans already. GoGM does not make root spans, only child spans
		OpentracingEnabled: false,
	}

	// register all vertices and edges
	// this is so that GoGM doesn't have to do reflect processing of each edge in real time
	// use nil or gogm.DefaultPrimaryKeyStrategy if you only want graph ids
	// we are using the default key strategy since our vertices are using BaseNode
	_gogm, err := gogm.New(&config, gogm.DefaultPrimaryKeyStrategy, &VertexA{}, &VertexB{}, &EdgeC{})
	if err != nil {
		panic(err)
	}

	//param is readonly, we're going to make stuff so we're going to do read write
	sess, err := _gogm.NewSessionV2(gogm.SessionConfig{AccessMode: gogm.AccessModeWrite})
	if err != nil {
		panic(err)
	}

	//close the session
	defer sess.Close()

	aVal := &VertexA{
		TestField: "woo neo4j",
	}

	bVal := &VertexB{
		TestTime: time.Now().UTC(),
	}

	//set bi directional pointer
	bVal.Single = aVal
	aVal.SingleA = bVal

	err = sess.SaveDepth(context.Background(), aVal, 2)
	if err != nil {
		panic(err)
	}

	//load the object we just made (save will set the uuid)
	var readin VertexA
	err = sess.Load(context.Background(), &readin, aVal.UUID)
	if err != nil {
		panic(err)
	}

	fmt.Printf("%+v", readin)
}

Migrating from V1 to V2

Initialization

Initialization in gogm v1

config := gogm.Config{
    IndexStrategy: gogm.VALIDATE_INDEX, //other options are ASSERT_INDEX and IGNORE_INDEX
    PoolSize:      50,
    Port:          7687,
    IsCluster:     false, //tells it whether or not to use `bolt+routing`
    Host:          "0.0.0.0",
    Password:      "password",
    Username:      "neo4j",
}

err := gogm.Init(&config, &VertexA{}, &VertexB{}, &EdgeC{})
if err != nil {
    panic(err)
}

Equivalent in GoGM v2

// define your configuration
config := gogm.Config{
    IndexStrategy: gogm.VALIDATE_INDEX, //other options are ASSERT_INDEX and IGNORE_INDEX
    PoolSize:      50,
    Port:          7687,
    IsCluster:     false, //tells it whether or not to use `bolt+routing`
    Host:          "0.0.0.0",
    Password:      "password",
    Username:      "neo4j",
}

	// register all vertices and edges
	// this is so that GoGM doesn't have to do reflect processing of each edge in real time
	// use nil or gogm.DefaultPrimaryKeyStrategy if you only want graph ids
	_gogm, err := gogm.New(&config, gogm.UUIDPrimaryKeyStrategy, &VertexA{}, &VertexB{}, &EdgeC{})
	if err != nil {
		panic(err)
	}
	
	gogm.SetGlobalGoGM(_gogm)
Note that we call gogm.SetGloablGogm so that we can still access it from a package level

Create a session

Creating a session in GoGM v1

sess, err := gogm.NewSession(false)
Note this still works in v2, its using the global gogm to create the session. Also note this is making an instance of the deprecated ISession

Equivalent in GoGM v2

// this would also work with a local instance of gogm (localGogm.NewSessionV2)
sess, err := gogm.G().NewSessionV2(gogm.SessionConfig{AccessMode: gogm.AccessModeWrite})

Summary

  • Minimal change requires creating a global gogm, everything else should still work with ISession (gogm v1 session object)
  • ISession is now deprecated but still supported
  • SessionV2 is the new standard

GoGM CLI

CLI Installation

go get -u github.com/mindstand/gogm/v2/cli/gogmcli

CLI Usage

NAME:
   gogmcli - used for neo4j operations from gogm schema

USAGE:
   gogmcli [global options] command [command options] [arguments...]

VERSION:
   2.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

  • Schema Migration
  • Errors overhaul using go 1.13 error wrapping
  • TLS Support

How you can help

  • Report Bugs
  • Fix bugs
  • Contribute (refer to contribute.md)
Owner
MindStand Technologies
MindStand provides an AI platform to identify hate speech, harassment and other troubling behaviors within coorperate and educational communication systems
MindStand Technologies
Comments
  • Match multiple patterns (OPTIONAL MATCH)

    Match multiple patterns (OPTIONAL MATCH)

    I'm trying to execute the following query:

    func FindUserFusionNetwork(sess gogm.SessionV2, discordTag string) (fusions []structures.Fusion) {
    	query := `MATCH (i:Item)-[fr:FUSION_INPUT|FUSION_RESULT]-(f:Fusion) ` +
    		`OPTIONAL MATCH (u:User{discordUsername: $discordTag})-[ui:INVENTORY_ITEM]->(i) ` +
    		`RETURN *`
    	err = sess.Query(context.Background(), query, map[string]interface{}{"discordTag": discordTag}, &fusions)
            fmt.Println(err)
    	return
    }
    

    While it works perfectly in the Neo4J Browser, gogm (v2) seems unable to extract the requested structure from the resultset. Am I doing something wrong ?

    Edit: It returns the following error: failed auto read tx, label not found for node [1281], gogm: internal error, but the node having id = 1281 is an Item node, shouldn't it hydrate the Fusion structure based on it ?.

  • invalid memory address or nil pointer dereference

    invalid memory address or nil pointer dereference

    Hi, I'm getting constant panics with specified error message. By looking at stack trace it's obvious that it's not on my side, because I do check everything to not be nil, before calling any gogm methods. It happens only on session.QueryRaw or session.Query functions. As a note I also check session for nil value before calling any of this functions, but still panics are happening. Please take a look at it, as it's critical bug.

    runtime error: invalid memory address or nil pointer dereference
    
    goroutine 155 [running]:
    runtime/debug.Stack(0x1, 0x0, 0x0)
    	/usr/local/go/src/runtime/debug/stack.go:24 +0x9f
    runtime/debug.PrintStack()
    	/usr/local/go/src/runtime/debug/stack.go:16 +0x25
    panic(0xac42c0, 0xfd92b0)
    	/usr/local/go/src/runtime/panic.go:969 +0x1b9
    github.com/neo4j/neo4j-go-driver/neo4j.(*result).doFetch(0xc00060fdd0, 0xae28a0)
    	/home/bezhan/Programming/go/pkg/mod/github.com/neo4j/[email protected]/neo4j/result.go:73 +0x32
    github.com/neo4j/neo4j-go-driver/neo4j.(*result).Next(0xc00060fdd0, 0x1b0)
    	/home/bezhan/Programming/go/pkg/mod/github.com/neo4j/[email protected]/neo4j/result.go:96 +0x146
    github.com/mindstand/gogm.(*Session).QueryRaw(0xc00008f590, 0xc00071f500, 0x1b0, 0xc000325320, 0x1b0, 0xb2a940, 0xc00032bba0, 0x0, 0x0)
    
    ...
    
    

    This is how I call QueryRaw function and panics happen:

    if sess == nil {
        return
    }
    sess.QueryRaw("MATCH ...", map[string]interface{}{}) // panic happens on this line
    
  • Relationship directional errors

    Relationship directional errors

    Bug Report:

    When trying to run with the code below, I get errors regarding the directions of the relationships on pretty much each of the relationships. However, another weird thing occurs, if I run the snippet multiple times, I get a revolving set of errors. Without editing the file at all, the errors would range from the relationship on SUPPORTS, OWNS, etc etc, but never the same one after every run. Which has made debugging this thing a headache and a half - I'm not sure where my screw up is, but for some reason I can't get anywhere past this:

    package domain
    
    import (
        "fmt"
        "reflect"
    
        "github.com/mindstand/gogm/v2"
    )
    
    type DevelopmentTeam struct {
        gogm.BaseNode
    
        Name             string   `gogm:"name=name" json:"name"`
        DistributionList string   `gogm:"name=dl" json:"distribution_list"`
        Shortname        string   `gogm:"name=shortname" json:"shortname"`
        ManagerPid       string   `gogm:"name=manager" json:"manager_pid"`
        MemberPids       []string `gogm:"name=member;properties" json:"member_pids"`
    
        Owns []*Repository `gogm:"direction=outgoing;relationship=OWNS" json:"-"`
    }
    
    type OperationsTeam struct {
        gogm.BaseNode
    
        Name             string   `gogm:"name=name" json:"name"`
        DistributionList string   `gogm:"name=dl" json:"distribution_list"`
        Shortname        string   `gogm:"name=shortname" json:"shortname"`
        ManagerPid       string   `gogm:"name=manager" json:"manager_pid"`
        MemberPids       []string `gogm:"name=member;properties" json:"member_pids"`
    
        Repositories []*Repository `gogm:"direction=outgoing;relationship=SUPPORTS" json:"-"`
    }
    
    type Repository struct {
        gogm.BaseNode
    
        Name                string   `gogm:"name=name" json:"name"`
        RepositoryId        string   `gogm:"name=repositoryid" json:"repository_id"`
        GitUrl              string   `gogm:"name=url" json:"git_url"`
        Tags                []string `gogm:"name=tags;properties" json:"tags"`
        GitlabDirectoryPath string   `gogm:"name=path" json:"path"`
        DocumentationLink   string   `gogm:"name=documentation" json:"documentation_link"`
    
        Deploys []*Service `gogm:"direction=outgoing;relationship=DEPLOYS" json:"-"`
    }
    
    type Service struct {
        gogm.BaseNode
    
        Name       string `gogm:"name=name" json:"name"`
        NexusLink  string `gogm:"name=nexus" json:"nexus_link"`
        HomeFolder string `gogm:"name=folder" json:"home_folder"`
    
        Utilizes       []*ServiceAccount     `gogm:"direction=outgoing;relationship=UTILIZES" json:"-"`
        Exposes        []*Endpoint           `gogm:"direction=outgoing;relationship=EXPOSES" json:"-"`
        ContingentUpon []*ContingentUponEdge `gogm:"direction=outgoing;relationship=CONTINGENT_UPON" json:"-"`
    }
    
    type ServiceAccount struct {
        gogm.BaseNode
    
        Name             string `gogm:"name=name" json:"name"`
        System           string `gogm:"name=datacenter" json:"data_center"`
        Environment      string `gogm:"name=environment" json:"environment"`
        DeploymentStyle  string `gogm:"name=deployment" json:"deployment_style"`
        EscalationTeamDl string `gogm:"name=escalation" json:"escalation_team_dl"`
    }
    
    type Endpoint struct {
        gogm.BaseNode
    
        Name                   string `gogm:"name=name" json:"name"`
        InfrastructureLocation string `gogm:"name=location" json:"infrastructure_location"`
        Datacenter             string `gogm:"name=datacenter" json:"data_center"`
        BaseUrl                string `gogm:"name=url" json:"base_url"`
        Environment            string `gogm:"name=environment" json:"environment"`
    
        Provides       []*Healthcheck        `gogm:"direction=outgoing;relationship=PROVIDES" json:"-"`
        ContingentUpon []*ContingentUponEdge `gogm:"direction=incoming;relationship=CONTINGENT_UPON" json:"-"`
    }
    
    type Healthcheck struct {
        gogm.BaseNode
    
        Name        string `gogm:"name=name" json:"name"`
        RequestType string `gogm:"name=type" json:"request_type"`
        UrlToAppend string `gogm:"name=url" json:"url_to_append"`
        Metadata    string `gogm:"name=metadata" json:"metadata"` // should be a struct but idk how to Go :')
        Headers     string `gogm:"name=header" json:"header"`
    }
    
    type ContingentUponEdge struct {
        gogm.BaseNode
    
        Start *Service  `json:"service"`
        End   *Endpoint `json:"endpoint"`
        // Criticality string    `gogm:"name=criticality"`
    }
    
    func (a *ContingentUponEdge) GetStartNode() interface{} {
        return a.Start
    }
    
    func (a *ContingentUponEdge) GetStartNodeType() reflect.Type {
        return reflect.TypeOf(&Service{})
    }
    
    func (a *ContingentUponEdge) SetStartNode(v interface{}) error {
        s, ok := v.(*Service)
        if !ok {
            return fmt.Errorf("cannot cast %T to *Service", s)
        }
    
        a.Start = s
        return nil
    }
    
    func (a *ContingentUponEdge) GetEndNode() interface{} {
        return a.End
    }
    
    func (a *ContingentUponEdge) GetEndNodeType() reflect.Type {
        return reflect.TypeOf(&Endpoint{})
    }
    
    func (a *ContingentUponEdge) SetEndNode(v interface{}) error {
        e, ok := v.(*Endpoint)
        if !ok {
            return fmt.Errorf("cannot cast %T to *Endpoint", e)
        }
    
        a.End = e
        return nil
    }
    

    Expected Behavior

    Current Behavior

    Errors like:

    failed to validate edges, invalid directional configuration on relationship [SUPPORTS], gogm: struct validation error
    panic: failed to init gogm instance, failed to validate edges, invalid directional configuration on relationship [SUPPORTS], gogm: struct validation error
    

    Steps to Reproduce

    1. Use the code snippet above and initialize with:
        config := gogm.Config{
            IndexStrategy: gogm.VALIDATE_INDEX,
            Host:          "0.0.0.0",
            Port:          7687,
            Username:      "neo4j",
            LogLevel:      "DEBUG",
            Password:      "fourj",
            PoolSize:      50,
            LoadStrategy:  gogm.PATH_LOAD_STRATEGY,
            // Encrypted:     false,
        }
    
        _gogm, err := gogm.New(&config, gogm.DefaultPrimaryKeyStrategy, &domain.DevelopmentTeam{},
            &domain.OperationsTeam{}, &domain.Endpoint{}, &domain.Healthcheck{}, &domain.Repository{},
            &domain.ServiceAccount{})
    

    Possible Solution

    N/A

    Environment

    | | Value | |------------------|-------| | Go Version | 1.17.3 | | GoGM Version | /v2 v2.3.2 | | Neo4J Version | 4.2.15 | | Operating System | Windows |

    Would you be interested in tackling this issue

    No

    Also, I don't see anywhere to reach out for general help other than the issues page. I imagine the errors are because of my short understanding of the project, so it would be beneficial just to have a forum to ask general question instead of a full-on issue

  • Cannot Build: 'imported and not used' error

    Cannot Build: 'imported and not used' error

    Bug Report:

    go get -u ./... go mod tidy

    run test file that imports gogm via intellij IDE

    I get:

    github.com/mindstand/gogm/v2

    /.../go/pkg/mod/github.com/mindstand/gogm/[email protected]/index_v3.go:27:2: imported and not used: "github.com/adam-hanna/arrayOperations" as go2 /.../go/pkg/mod/github.com/mindstand/gogm/[email protected]/index_v3.go:277:18: undefined: arrayOperations .../go/pkg/mod/github.com/mindstand/gogm/[email protected]/index_v3.go:288:17: undefined: arrayOperations /.../go/pkg/mod/github.com/mindstand/gogm/[email protected]/index_v4.go:27:2: imported and not used: "github.com/adam-hanna/arrayOperations" as go2 /.../go/pkg/mod/github.com/mindstand/gogm/[email protected]/index_v4.go:306:18: undefined: arrayOperations /.../go/pkg/mod/github.com/mindstand/gogm/[email protected]/index_v4.go:317:17: undefined: arrayOperations

    Expected Behavior

    code should compile

    Current Behavior

    as above

    Steps to Reproduce

    package neo4jPackages
    
    import (
    	"github.com/mindstand/gogm/v2"
    )
    
    //Package is a software package
    type Package struct {
    	gogm.BaseNode
    	Name            string                 `gogm:"name=name;index;unique"` 
    }
    
    ## Possible Solution
    See above
    
    ## Environment
    |                  | Value |
    |------------------|-------|
    | Go Version       |    1.18   |
    | GoGM Version     |  2.3.5     |
    | Neo4J Version    |  Not got that far!     |
    | Operating System |  Linux (Kubuntu)     |
    
    ## Would you be interested in tackling this issue
    No - This is down to some import required at core level I think
    
  • Multi Databases Support

    Multi Databases Support

    Added a database param used by the Session Config

    Also changed neo4j.Session with neo4j.NewSession to be able to pass a configuration. Might be more scalable if we pass that same Configuration at the gogm.NewSession level but to keep it simple, for the first iteration, I think this is ok

  • Support multiple labels / default label

    Support multiple labels / default label

    Do you have any plans of supporting multiple labels for nodes (structures) or some default(main) label for struct?

    GOGM uses first label of node. This leads to difficulties if additional labels are used. For example:

    n1:Sensor
    n2:Active:Sensor
    

    The main label/entity is Sensor. In both cases it will be useful to load struct by GOGM (without any raw queries). But currently for the second case you need to have Active struct that is same as Sensor (with same links and etc).

    Some config for structs with main labels or with ignoring labels by GOGM will be really useful.

  • Pagination validation contradict with ordering validation

    Pagination validation contradict with ordering validation

    I assume there is an error in Validate function for pagination or in the logic of LoadAllDepthFilterPagination. To build query you need to pass parameters for ordering and paging (LoadAllDepthFilterPagination), but Validate function has such implementation

    func (p *Pagination) Validate() error {
    	if p.PageNumber >= 0 && p.LimitPerPage > 1 && p.OrderByField != "" && p.OrderByVarName != "" {
    		return errors.New("pagination configuration invalid, please double check")
    	}
    
    	return nil
    }
    

    You can pass parameters for ordering or paging, but not both. In other case you should use LimitPerPage = 0 to pass validation. As a result you can get only an error or empty result in such implementation:

    //if the query requires pagination, set that up
    	if pagination != nil {
    		err := pagination.Validate()
    		if err != nil {
    			return err
    		}
    
    		query = query.
    			OrderBy(dsl.OrderByConfig{
    				Name:   pagination.OrderByVarName,
    				Member: pagination.OrderByField,
    				Desc:   pagination.OrderByDesc,
    			}).
    			Skip(pagination.LimitPerPage * pagination.PageNumber).
    			Limit(pagination.LimitPerPage)
    	}
    

    Results:

    • Without pagination: sess.LoadAllDepthFilterPagination(&sensors, 1, cond, nil, &gogm.Pagination{OrderByField: "name", OrderByVarName: "n"}) - query works but has LIMIT and SKIP equal 0 - MATCH p=(n)-[*0..1]-() WHERE ... RETURN p ORDER BY n.name SKIP 0 LIMIT 0
    • Without ordering: sess.LoadAllDepthFilterPagination(&sensors, 1, cond, nil, &gogm.Pagination{LimitPerPage: 100, PageNumber: 0}) - OrderBy gives error - errors found: name and member have to be defined -- total errors (1)
    • With ordering and paging - sess.LoadAllDepthFilterPagination(&sensors, 1, cond, nil, &gogm.Pagination{LimitPerPage: 100, PageNumber: 0, OrderByField: "name", OrderByVarName: "n"} - pagination validation gives error - pagination configuration invalid, please double check
  • Schema Load Strategy

    Schema Load Strategy

    Schema load strategy implementation based on the neo4j-ogm's implementation (see https://github.com/neo4j/neo4j-ogm/blob/934dcd23825a32f6c3538dba8e2f8d6834540fdd/core/src/main/java/org/neo4j/ogm/session/request/strategy/impl/AbstractSchemaLoadClauseBuilder.java#L45). SchemaLoadStrategy uses the OGM's representation to build load queries that execute much more efficiently then PathLoadStrategy.

    Progress:

    • [x] SchemaLoadStrategy implementation based on neo4j-ogm
    • [x] Tests verifying expected generated queries
    • [x] Allow for a session's LoadStrategy to be specified or changed
    • [x] End to end tests verifying decode logic
    • [x] Adjust decode logic to handle nested path comprehension queries

    Unrelated changes:

    • Documentation change (fixes #80)
    • #65
    • Miscellaneous cleanup
  • Merge instead of create

    Merge instead of create

    Hi, I am trying to understand how one should update existing nodes with gogm. If I try the minimal examples multiple times it creates the nodes several times. In the source code I find several references where the creation and update is differentiated.

    As I don't know the nodes' IDs (in order to call sess.Load()), my only idea would be to write custom queries for everything. This does not seem to be the intention behind the project though.

    Can you provide a minimal example with update functionality? Thank you very much!

  • Relationship within a single type

    Relationship within a single type

    I'd like to create a relationship that references the same type:

    type User struct {
    	[...]
    
    	Friends []*User `gogm:"relationship=USER_FRIENDS;direction=both"
    }
    

    Whenever I try starting the app, validation fails: "failed to init gogm instance, failed to validate edges, invalid directional configuration on relationship [USER_FRIENDS], gogm: struct validation error"

    I've tried using an edge and every variation of direction with no success. Am I missing something, or is this not supported?

  • load config not found for node [dc994ea0-d5b6-4cbc-b597-53d335b409b9]

    load config not found for node [dc994ea0-d5b6-4cbc-b597-53d335b409b9]

    I created some node event with [ff733301-4d3c-4892-8bfe-7d2c440edb43] by err = sess.Save(&event). Then I created another node action [dc994ea0-d5b6-4cbc-b597-53d335b409b9] which is connected with event by some relation and saved branch event--HAS->Action by err = sess.SaveDepth(&event, 2) - all is ok.

    But when I tried to create another pair (with different UUIDs) event--HAS->Action and saved it by err = sess.SaveDepth(&event, 2) I got

    load config not found for node [dc994ea0-d5b6-4cbc-b597-53d335b409b9]
    

    ... but [dc994ea0-d5b6-4cbc-b597-53d335b409b9] is UUID from previous pair.

    What should I check in this case?

  • Cannot auto-read pointers to basic types

    Cannot auto-read pointers to basic types

    Bug Report:

    Failure to auto-read tx in case of a pointer to a basic type (failed to convert node to value, recovered converToValue: reflect.Value.Convert: value of type string cannot be converted to type *string)

    Issue Description

    I am currently auto-generating types form gqlgen and adding gogm directives on top of those. However, Optional values are emitted as pointers, which are nil in case the field is empty. As such, if a node has a field of type *string, this fails to deserialise, however succeeds if the field is declared as string.

    Steps to Reproduce

    Insert and lookup a node like the one below

    type Test struct {
    	gogm.BaseUUIDNode
    	Name     *string      `json:"name,omitempty" gogm:"name=name"`
    	Type     string    `json:"type" gogm:"name=type"`
    }
    

    Environment

    | | Value | |------------------|-------| | Go Version | 1.19.2 | | GoGM Version | 2.3.6 | | Neo4J Version | 4.4.0 | | Operating System | darwin/arm64 |

    Would you be interested in tackling this issue

    Yes

  • Installation issues

    Installation issues

    How did you go about installing the command line portion of gogm. I am running gogmcli g . after installing but I am recieveing a 'gogmcli' is not recogmized as the name of a cmdlet error,

  • Failed to reverse child/parent relation

    Failed to reverse child/parent relation

    Bug Report:

    SaveDepth failed to save entity when you try to reverse relation with Link/Unlink function. E.g. you have (node1)-[HAS_NODE]->(node2) and you wand to reverse relation to have (node1)<-[HAS_NODE]-(node2):

    node2.UnlinkParent(node1)
    node2.LinkChild(node1)
    SaveDepth(node2) - error
    

    Expected Behavior

    After changing relation with Link/Unlink functions, SaveDepth function works without errors and relation is changed.

    Current Behavior

    SaveDepth gives failed to save in auto transaction, expected relationship deletions not equal to actual. Expected=2|Actual=1

    Steps to Reproduce

    Code example in the end.

    1. Use structure
    type Node struct {
    	gogm.BaseUUIDNode
    	Name     string  `gogm:"name=name"`
    	Children []*Node `gogm:"direction=outgoing;relationship=HAS_NODE"`
    	Parents  []*Node `gogm:"direction=incoming;relationship=HAS_NODE"`
    }
    
    1. Create functions with gogmcli
    2. Create 2 nodes
    3. Add node2 as child for node1
    4. Save node2 with depth 1
    5. Load node2
    6. Remove parent from node2 and add children:
    node2.UnlinkFromNodeOnFieldParents(parent)
    node2..LinkToNodeOnFieldChildren(parent)
    
    1. Save node2 with depth 1.

    Possible Solution

    Environment

    | | Value | |------------------|-------| | Go Version | go version go1.18.3 windows/amd64 | | GoGM Version | v2.3.6 | | Neo4J Version | Version: 4.4.3 Edition: community | | Operating System | Win10 x64 |

    Would you be interested in tackling this issue

    No

    Additional info

            n1 := &Node{Name: "n1"}
    	n2 := &Node{Name: "n2"}
    	n1.LinkToNodeOnFieldChildren(n2)
    
    	err = sess.SaveDepth(ctx, n1, 1)
    	if err != nil {
    		log.Err(err).Msg("")
    		return
    	}
    
    	n22 := &Node{}
    	err = sess.LoadDepth(ctx, n22, n2.UUID, 1)
    	if err != nil {
    		log.Err(err).Msg("")
    		return
    	}
    	fmt.Printf("Parents: %d\n", len(n22.Parents))   // 1
    	fmt.Printf("Children: %d\n", len(n22.Children)) // 0
    
    	parent := n22.Parents[0]
    	n22.UnlinkFromNodeOnFieldParents(parent)
    	n22.LinkToNodeOnFieldChildren(parent)
    	err = sess.SaveDepth(ctx, n22, 1)
    	// failed to save in auto transaction, expected relationship deletions not equal to actual. Expected=2|Actual=1
    	if err != nil {
    		log.Err(err).Msg("")
    	}
    
    	// Works fine if you save child and reload parent node between unlink/link
    
    	n22 = &Node{}
    	err = sess.LoadDepth(ctx, n22, n2.UUID, 1)
    	if err != nil {
    		log.Err(err).Msg("")
    		return
    	}
    
    	parent = n22.Parents[0]
    	err = n22.UnlinkFromNodeOnFieldParents(parent)
    	err = sess.SaveDepth(ctx, n22, 1) 
    	if err != nil {
    		log.Err(err).Msg("")
    	}
    
    	err = sess.LoadDepth(ctx, parent, parent.UUID, 1)
    	if err != nil {
    		log.Err(err).Msg("")
    	}
    
    	n22.LinkToNodeOnFieldChildren(parent)
    	err = sess.SaveDepth(ctx, n22, 1)
    	if err != nil {
    		log.Err(err).Msg("")
    	}
    
  • Fail to run example

    Fail to run example

    Bug Report:

    I ran the example code, and got a panic panic: failed to init gogm instance, failed to init indices, failed to verify all indexes and contraints, failed to verify indexes and constraints for db neo4j on db version 4+, failed to convert result to string array, result is nil

    Expected Behavior

    Current Behavior

    Steps to Reproduce

    Possible Solution

    Environment

    | | Value | |------------------|-------| | Go Version | 1.18 | | GoGM Version | 2.3.6 | | Neo4J Version | 4.4.5 | | Operating System | Mac M1 chip |

    Would you be interested in tackling this issue

    Yes / No

  • Incorrect mapping parents/children nodes (same relation, same node) of cypher results into structures

    Incorrect mapping parents/children nodes (same relation, same node) of cypher results into structures

    Bug Report:

    Mapping results into structures adds created structures from relations into main result slice (checked on SCHEMA_LOAD_STRATEGY only). E.g. we have such graph image and such structure

    type Node struct {
    		gogm.BaseNode
    		Name     string  `gogm:"name=name"`
    		Children []*Node `gogm:"direction=outgoing;relationship=HAS_NODE"`
    		Parents  []*Node `gogm:"direction=incoming;relationship=HAS_NODE"`
    	}
    

    when loading 1 node by filter (name=n1) and depth=1 (LoadAllDepthFilter(context.Background(), &nodes, 1, filter, nil)) , you get all 3 nodes in the results - nodes slice (see full example in the end).

    Expected Behavior

    nodes slice contains 1 node with name=n1 and this node has other 2 nodes in Children and Parents slices. nodes should not contain nodes with other name value.

    Current Behavior

    nodes slice contains 3 node.

    Steps to Reproduce

    1. Create Graph structure in the Neo4j
    create  (`14` :Node {name:'n1'}) ,
      (`15` :Node {name:'n2'}) ,
      (`16` :Node {name:'n3'}) ,
      (`14`)-[:`HAS_NODE` ]->(`15`),
      (`16`)-[:`HAS_NODE` ]->(`14`)
    
    1. Create a filter to get node with name=n1
    filter := gocypherdsl.
    		C(&gocypherdsl.ConditionConfig{Name: "n", Label: "Node"}).
    		And(&gocypherdsl.ConditionConfig{
    			Name:              "n",
    			Field:             "name",
    			ConditionOperator: gocypherdsl.EqualToOperator,
    			Check:             "n1",
    		})
    
    1. Use LoadAllDepthFilter(context.Background(), &nodes, 1, filter, nil) to load nodes.

    Possible Solution

    I think some problem with mapping Cypher results into structures. Query works fine (has 1 node with two relations), but mapping of results adds all Node into main result slice.

    MATCH (n:Node) WHERE n:Node AND n.name = 'n1' RETURN n , [[(n)-[r_H_1:HAS_NODE]->(n_N_1:Node) | [r_H_1, n_N_1]], [(n)<-[r_H_1:HAS_NODE]-(n_N_1:Node) | [r_H_1, n_N_1]]]
    

    It is necessary to add structures from relations to the corresponding slices of the found structures, but not to the main results (If the structure does not match the filter).

    Environment

    | | Value | |------------------|-------| | Go Version | go1.18.3 windows/amd64 | | GoGM Version | v2.3.6 | | Neo4J Version | 4.4.3 community | | Operating System |Win 10 |

    Would you be interested in tackling this issue

    No

    Additional info

    Code example:

    package main
    
    import (
    	"context"
    	"fmt"
    	gocypherdsl "github.com/mindstand/go-cypherdsl"
    	"github.com/mindstand/gogm/v2"
    	"github.com/rs/zerolog/log"
    )
    
    func testSameRef() {
    	type Node struct {
    		gogm.BaseNode
    		Name     string  `gogm:"name=name"`
    		Children []*Node `gogm:"direction=outgoing;relationship=HAS_NODE"`
    		Parents  []*Node `gogm:"direction=incoming;relationship=HAS_NODE"`
    	}
    
    	g, err := gogm.New(
    		&gogm.Config{
    			Host:          "127.0.0.1",
    			Port:          7688,
    			Username:      "neo4j",
    			Password:      "neo4j1",
    			PoolSize:      50,
    			Logger:        gogm.GetDefaultLogger(),
    			LogLevel:      "DEBUG",
    			IndexStrategy: gogm.IGNORE_INDEX,
    			LoadStrategy:  gogm.SCHEMA_LOAD_STRATEGY,
    		},
    		gogm.DefaultPrimaryKeyStrategy,
    		&Node{},
    	)
    	if err != nil {
    		log.Err(err).Msg("")
    		return
    	}
    
    	sess, err := g.NewSessionV2(gogm.SessionConfig{AccessMode: gogm.AccessModeRead})
    	if err != nil {
    		log.Err(err).Msg("")
    		return
    	}
    
    	filter := gocypherdsl.
    		C(&gocypherdsl.ConditionConfig{Name: "n", Label: "Node"}).
    		And(&gocypherdsl.ConditionConfig{
    			Name:              "n",
    			Field:             "name",
    			ConditionOperator: gocypherdsl.EqualToOperator,
    			Check:             "n1",
    		})
    	if err != nil {
    		log.Err(err).Msg("")
    		return
    	}
    
    	var nodes []*Node
    	err = sess.LoadAllDepthFilter(context.Background(), &nodes, 1, filter, nil)
    	if err != nil {
    		log.Err(err).Msg("")
    		return
    	}
    
    	// Gives
    	// 1 name: n1
    	// 2 name: n2
    	// 3 name: n3
    	for i, node := range nodes {
    		fmt.Printf("%d name: %s\n", i+1, node.Name)
    	}
    }
    
SQL mapper ORM framework for Golang
 SQL mapper ORM framework for Golang

SQL mapper ORM framework for Golang English 中文 Please read the documentation website carefully when using the tutorial. DOC Powerful Features High Per

Dec 8, 2021
Mybatis for golang - SQL mapper ORM framework

SQL mapper ORM framework for Golang English 中文 Please read the documentation website carefully when using the tutorial. DOC Powerful Features High Per

Nov 10, 2022
100% type-safe ORM for Go (Golang) with code generation and MySQL, PostgreSQL, Sqlite3, SQL Server support. GORM under the hood.

go-queryset 100% type-safe ORM for Go (Golang) with code generation and MySQL, PostgreSQL, Sqlite3, SQL Server support. GORM under the hood. Contents

Dec 30, 2022
Golang Event Scheduling Sample Using Postgresql Database as persisting layer.

Database Based Event Scheduling Example that demonstrates super basic database based event scheduling. To run this example; Copy .env.example to .env

Nov 28, 2022
A pure golang SQL database for learning database theory

Go SQL DB 中文 "Go SQL DB" is a relational database that supports SQL queries for research purposes. The main goal is to show the basic principles and k

Dec 29, 2022
An orm library support nGQL for Golang

norm An ORM library support nGQL for Golang. Overview Build insert nGQL by struct / map (Support vertex, edge). Parse Nebula execute result to struct

Dec 1, 2022
golang orm

korm golang orm, 一个简单易用的orm, 支持嵌套事务 安装 go get github.com/wdaglb/korm go get github.com/go-sql-driver/mysql 支持数据库 mysql https://github.com/go-sql-driv

Oct 31, 2022
Golang ORM with focus on PostgreSQL features and performance

go-pg is in a maintenance mode and only critical issues are addressed. New development happens in Bun repo which offers similar functionality but works with PostgreSQL, MySQL, and SQLite.

Jan 8, 2023
Very simple example of golang buffalo CRUD

Buffalo CRUD exemple Introduction Site du projet : https://gobuffalo.io/fr Documentation générale : https://gobuffalo.io/fr/docs/overview/ Documentati

Nov 7, 2021
The fantastic ORM library for Golang, aims to be developer friendly

GORM The fantastic ORM library for Golang, aims to be developer friendly. Overview Full-Featured ORM Associations (Has One, Has Many, Belongs To, Many

Nov 11, 2021
Automatically generate tags for golang struct.

gtag is a command tool that can automatically generate tags for golang struct. Quick start Install gtag into your GOPATH go install github.com/sycki/g

Feb 12, 2022
Golang mysql orm, a personal learning project, dedicated to easy use of mysql

golang mysql orm 个人学习项目, 一个易于使用的mysql-orm mapping struct to mysql table golang结构

Dec 30, 2021
Go-mysql-orm - Golang mysql orm,dedicated to easy use of mysql

golang mysql orm 个人学习项目, 一个易于使用的mysql-orm mapping struct to mysql table golang结构

Jan 7, 2023
Query: A Simple db helper with golang

Query: A Simple db helper with golang

Jul 20, 2022
Api-project - Api project with Golang, Gorm, Gorilla-Mux, Postgresql

TECHNOLOGIES GOLANG 1.14 GORM GORILLA-MUX POSTGRESQL API's PATHS For Product Ser

Nov 23, 2022
EZCoin is a control panel for Bitfinex funding, backend is build by Golang, Gin and GORM, frontend is build by angular

EZCoin server is backend for Bitfinex funding, it build by Golang, Gin and GORM.

Feb 7, 2022
Sample code snippet to familiarize golang . Concept of goroutines , channels, concurrency is implemented in a sample scenario

go-mysql-goroutines-channel Sample code snippet to familiarize golang . Concept of goroutines , channels, concurrency , interface, slice, error handli

May 29, 2022
Neo4j REST Client in golang

DEPRECATED! Consider these instead: https://github.com/johnnadratowski/golang-neo4j-bolt-driver https://github.com/go-cq/cq Install: If you don't ha

Nov 9, 2022
Neo4j client for Golang

neoism - Neo4j client for Go Package neoism is a Go client library providing access to the Neo4j graph database via its REST API. Status System Status

Dec 30, 2022
Neo4j Rest API Client for Go lang

neo4j.go Implementation of client package for communication with Neo4j Rest API. For more information and documentation please read Godoc Neo4j Page s

Nov 9, 2022