neo4j / neo4j-go-driver

Neo4j Bolt Driver for Go
Apache License 2.0
482 stars 68 forks source link

The same Cypher query yields inconsistent results between the SDK and the browser, with several Relationships missing. #557

Closed SPuerBRead closed 7 months ago

SPuerBRead commented 7 months ago

I suspect that I have encountered a bug. When querying with the Go SDK, the data results obtained are inconsistent with those in the Neo4j browser, with several Relationships missing.

Neo4j Version: Neo4j Kernel 5.12.0 community Neo4j Mode: Single instance
Driver version: Go driver v5.12.0 Operating System: centos

My Cypher is

MATCH (n1:AAA_IngressOfSecurityGroup {AssetsAccountId:"aaaa"})-[:IngressOfSecurityGroup]->(n2:AAA_SecurityGroup {AssetsAccountId:"aaaa"})-[:SecurityGroupOfNetworkInterface]->(n3:AAA_NetworkInterface {AssetsAccountId:"aaaa"})-[:NetworkInterfaceOfCvm]->(n4:AAA_CvmInstance {AssetsAccountId:"aaaa",AssetUniqueId:"aaaa-165d2740e7fa",AssetsVersion:"1702108924"}) WHERE n4.InstanceState = 'RUNNING' AND n4.RestrictState = 'NORMAL' AND n1.Action = 'ACCEPT' AND (n1.Ipv6CidrBlock = "::/0" OR n1.CidrBlock = "0.0.0.0/0") AND toLower(n1.Port) = 'all' AND (toLower(n1.Protocol) = 'tcp' or toLower(n1.Protocol) = 'udp' or toLower(n1.Protocol)= 'all' )   WITH n1, n2, n3, n4 WHERE ALL (groupId IN n3.GroupSet WHERE   NOT EXISTS {     MATCH (n2_other:AAA_SecurityGroup {AssetsAccountId:"aaaa", SecurityGroupId: groupId})-[:IngressOfSecurityGroup]->(n1_other:AAA_IngressOfSecurityGroup)     WHERE n1_other.Action = 'DROP' AND ((n1_other.CidrBlock = "0.0.0.0/0" OR n1_other.Ipv6CidrBlock = "::/0") AND toLower(n1_other.Port) = 'all') AND NOT EXISTS {       MATCH (n1_smaller:AAA_IngressOfSecurityGroup)-[:IngressOfSecurityGroup]->(n2_other)       WHERE toInteger(n1_smaller.PolicyIndex) < toInteger(n1_other.PolicyIndex)     }   } )  OPTIONAL MATCH p2 = (n7:AAA_PublicIp {AssetsAccountId:"aaaa"})-[:PublicIpToVpc]->(n6:AAA_Vpc {AssetsAccountId:"aaaa"})-[:SubnetsInVPC]->(n5:AAA_Subnet {AssetsAccountId:"aaaa"})-[:NetworkInterfaceInSubnet]->(n3) WHERE p2 IS NOT NULL  OPTIONAL MATCH p3 = (n8:AAA_Eip {AssetsAccountId:"aaaa"})-[:EipOToVpc]->(n6)-[:SubnetsInVPC]->(n5)-[:NetworkInterfaceInSubnet]->(n3) WHERE p3 IS NOT NULL      WITH DISTINCT n4, n1, n2, n3, n6, n7, p2, p3  RETURN n1, n2, n3,n4, n6, n7, p2, p3

The code I use to process the data returned from Neo4j is as follows.

result, session, err := global.Neo4j.RunCypherQuery(body.Cypher, nil)
    if err != nil {
        logger.Log.Error("RunCypherQuery error: " + err.Error())
        response := responses.NewErrorResponse(responses.StatusCodeUnknownErr, err.Error())
        c.JSON(http.StatusOK, response)
        return
    }

    var nodes []neo4j.Node
    var relationships []neo4j.Relationship

    seenNodes := make(map[int64]bool)
    seenRelationships := make(map[int64]bool)
    for result.Next() {
        record := result.Record()
        for _, value := range record.Values {
            if value == nil {
                continue
            }
            switch v := value.(type) {
            case neo4j.Node:
                if _, found := seenNodes[v.Id]; !found {
                    seenNodes[v.GetId()] = true
                    nodes = append(nodes, v)
                }
            case neo4j.Relationship:
                if _, found := seenRelationships[v.Id]; !found {
                    seenRelationships[v.GetId()] = true
                    relationships = append(relationships, v)
                }
            case neo4j.Path:
                for _, node := range v.Nodes {
                    if _, found := seenNodes[node.Id]; !found {
                        seenNodes[node.GetId()] = true
                        nodes = append(nodes, node)
                    }
                }
                for _, rel := range v.Relationships {
                    if _, found := seenRelationships[rel.Id]; !found {
                        seenRelationships[rel.GetId()] = true
                        relationships = append(relationships, rel)
                    }
                }
            case []interface{}:
                for _, item := range v {
                    switch elem := item.(type) {
                    case neo4j.Node:
                        if _, found := seenNodes[elem.GetId()]; !found {
                            seenNodes[elem.GetId()] = true
                            nodes = append(nodes, elem)
                        }
                    case neo4j.Relationship:
                        if _, found := seenRelationships[elem.GetId()]; !found {
                            seenRelationships[elem.GetId()] = true
                            relationships = append(relationships, elem)
                        }
                    default:
                        logger.Log.Error("Unexpected type in []interface{}: %T", item)
                        response := responses.NewErrorResponse(responses.StatusCodeUnknownErr, errors.New(fmt.Sprintf("Unexpected type in []interface{}: %T", item)).Error())
                        c.JSON(http.StatusOK, response)
                        return
                    }
                }
            default:
                logger.Log.Error("Unexpected type: %T", v)
                response := responses.NewErrorResponse(responses.StatusCodeUnknownErr, errors.New(fmt.Sprintf("Unexpected type: %T", v)).Error())
                c.JSON(http.StatusOK, response)
                return
            }
        }
    }

func (c *Client) RunCypherQuery(query string, params map[string]interface{}) (neo4j.Result, neo4j.Session, error) {
    session := c.driver.NewSession(neo4j.SessionConfig{})
    fmt.Println(query)
    result, err := session.Run(query, params)
    if err != nil {
        return nil, session, err
    }
    return result, session, nil
}

in neo4j desktop search result is

image

in my code search result is

image

The position marked with a red "X" indicates that no results were found.

In the RunCypherQuery method, the data for these three Relationships were not found in the returned result.

SPuerBRead commented 7 months ago

In the topmost MATCH statement, I didn't define a name for the Relationships, such as r1, so the returned data did not include these relationships. Oddly enough, the browser managed to return data using the unnamed Cypher, which might involve some behind-the-scenes processing. By modifying the Cypher query, I have resolved this issue.

fbiville commented 7 months ago

@SPuerBRead by default, Neo4j Browser does some extra work to connect returned nodes. You can disable that behavior.

Screenshot 2023-12-11 at 10 10 29

Another option is to check the "Table" tab when you run your queries in browser, it will display the results in a way consistent with what you get from a driver.

SPuerBRead commented 7 months ago

@SPuerBRead by default, Neo4j Browser does some extra work to connect returned nodes. You can disable that behavior.

Screenshot 2023-12-11 at 10 10 29

Another option is to check the "Table" tab when you run your queries in browser, it will display the results in a way consistent with what you get from a driver.

Thank you for the explanation. I didn't notice this button before and thought I encountered a bug, my apologies.

fbiville commented 7 months ago

No worries, this behavior in Neo4j browser is both a nice feature for demos and a trap 😅 This is not the first time someone trips over it (count me in too 😄).