Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
lomoval opened this issue Jul 1, 2022 · 3 comments
Labels
bug Something isn't working

Comments

@lomoval
Copy link

lomoval commented Jul 1, 2022

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)
	}
}
@lomoval lomoval added the bug Something isn't working label Jul 1, 2022
@erictg
Copy link
Member

erictg commented Jul 13, 2022

Hi @lomoval, thank you for the thorough bug report and apologies for the delayed reply. I'm going to reproduce and debug this issue with the examples you provided. I will get back to you here with updates. Expect to hear something in the next couple of days!

@atymkiv
Copy link

atymkiv commented May 17, 2023

@erictg This worked fine for me with children of different type, but I got the same problem with Query method.
i.e. if you do smth like this:
user := new(model.User)
sess.Query(ctx, MATCH (u {user_id: $id})-[:OWNS]->(p:Product) RETURN u, COLLECT(p) as products, map[string]interface{}{"id": userID}, user)
user.Products are nil, though it returns products as separate nodes, just doesn't map them as relations.
While it's critical for me to have such behaviour, as I don't want to load all user's relations

@lomoval
Copy link
Author

lomoval commented May 17, 2023

@atymkiv
To map results of Cypher query into GOGM structures with relations you need to have info about relations in a response.
Collect only creates an array with nodes.
With SCHEMA_LOAD_STRATEGY you can try such query:

MATCH (u {user_id: $id}) RETURN u, [[(u)-[r:OWNS]->(p:Product) | [r, p]]]

In this case you will have u node and array with relation and nodes. Based on this info and internal config (struct fields - relation) GOGM will fill user.Products.
GOGM generates same queries but for all relations and for needed depth.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants