RedisGraph / redisgraph-go

A Golang client for redisgraph
https://redisgraph.io
BSD 3-Clause "New" or "Revised" License
132 stars 38 forks source link

Graph Commital not Visualizing within RedisInsight #80

Closed W-Lawless closed 1 year ago

W-Lawless commented 1 year ago

Hi,

I might not have properly understood the paradigm of the library, but I was looking to generalize the creation of nodes and edges through the use of builder/factory methods. So far as I can tell, there are no errors in my Go implementation, and the data does RETURN properly when I examine it through RedisInsight. However, the visualizer is broken. I can only view my data as raw text; which really defeats the purpose of loading everything into a graph in the first place.

My implementation isn't too long, but pulling off the various hat-tricks with generics and getting the types to play together did take the majority of the day.

Relevant types:

type Option[T comparable] struct {
    value T
    label string
    edges []string
}

type OptionsList[T comparable] struct {
    TypeLabel string
    Options   []Option[T]
}

type NodeList[T comparable] map[string]OptionNodeList[T]

type OptionNodeList[T comparable] map[T]*rg.Node

Node factories:

func BuildNodes[T comparable](optionList OptionsList[T], graph rg.Graph) NodeList[T] {
    nodesList := make(NodeList[T])
    emptyOptionList := make(OptionNodeList[T])

    for _, option := range optionList.Options {
        node := BuildNode[T](optionList.TypeLabel, option, graph)

        if nodesList[optionList.TypeLabel] == nil {
            nodesList[optionList.TypeLabel] = emptyOptionList
        }

        nodesList[optionList.TypeLabel][option.value] = node //STORE LIST OF NODES BY TYPE & VALUE
    }

    return nodesList
}

func BuildNode[T comparable](label string, option Option[T], graph rg.Graph) *rg.Node {
    //buildPropertyMap Here
    properties := map[string]interface{}{
        option.label: option.value,
    }
    //create node
    node := rg.Node{
        Label:      label,
        Properties: properties,
    }

    graph.AddNode(&node)

    return &node
}

Edge factories:

func MapEdges[T comparable](relationLabel string, optionList OptionsList[T], sourceList NodeList[T], destinationLabel string, destinationList NodeList[string], graph *rg.Graph) {
    for _, option := range optionList.Options {
        sourceNode := sourceList[optionList.TypeLabel][option.value]
        for _, edgeValue := range option.edges {
            destinationNode := destinationList[destinationLabel][edgeValue]
            CreateRelationship(sourceNode, relationLabel, destinationNode, graph)
        }
    }
}

func CreateRelationship(source *rg.Node, relationLabel string, destination *rg.Node, graph *rg.Graph) {
    // fmt.Println(source, relationLabel, destination)
    edge := rg.Edge{
        Source:      source,
        Relation:    relationLabel,
        Destination: destination,
    }
    graph.AddEdge(&edge)
}

The caller function:

func CreateGraph() {
    conn, _ := redis.Dial("tcp", REDIS_HOST)
    defer conn.Close()
    graph := rg.GraphNew("Configurations", conn)
    graph.Delete() //CLEAR OLD DATA FOR CLEAN SET UP

    variantOptions := OptionsList[string]{
        TypeLabel: "Variants",
        Options: []Option[string]{
            {label: "name", value: "A", edges: []string{}},
            {label: "name", value: "B", edges: []string{}},
            {label: "name", value: "C", edges: []string{}},
            {label: "name", value: "D", edges: []string{}},
        },
    }
    navOptions := OptionsList[int]{
        TypeLabel: "NavButtons",
        Options: []Option[int]{
            {label: "name", value: 5, edges: []string{"A", "C"}},
            {label: "name", value: 4, edges: []string{"B", "D"}},
        },
    }

    variantOptionNodes := BuildNodes[string](variantOptions, &graph)
    navOptionNodes := BuildNodes[int](navOptions, &graph)

    MapEdges[int]("COMPOSEDOF", navOptions, navOptionNodes, variantOptions.TypeLabel, variantOptionNodes, &graph)

    graph.Commit()
    query := `MATCH (n) RETURN n`
    queryRel := `MATCH (a)-[r]-(b) RETURN r`
    resultOne, _ := graph.Query(query)
    resultTwo, _ := graph.Query(queryRel)

    resultOne.PrettyPrint()
    resultTwo.PrettyPrint()

}

Result One:

+---------------+
|       n       |
+---------------+
| {name:"A"}    |
| {name:"B"}    |
| {name:"C"}    |
| {name:"D"}    |
| {name:5}      |
| {name:4}      |
+---------------+

Cached execution 0.000000
Query internal execution time 0.225416

Result Two:

+----+
| r  |
+----+
| {} |
| {} |
| {} |
| {} |
| {} |
| {} |
| {} |
| {} |
+----+

Cached execution 0.000000
Query internal execution time 0.409459

Result Two is curious, as RedisInsight returns this data for the same query:

1) 1) "r"
2) 1) 1) 1) 1) "id"
            2) "0"
         2) 1) "type"
            2) "COMPOSEDOF"
         3) 1) "src_node"
            2) "5"
         4) 1) "dest_node"
            2) "1"
         5) 1) "properties"
            2) (empty list or set)
   2) 1) 1) 1) "id"
            2) "2"
         2) 1) "type"
            2) "COMPOSEDOF"
         3) 1) "src_node"
            2) "6"
         4) 1) "dest_node"
            2) "2"
         5) 1) "properties"
            2) (empty list or set)
   3) 1) 1) 1) "id"
            2) "1"
         2) 1) "type"
            2) "COMPOSEDOF"
         3) 1) "src_node"
            2) "5"
         4) 1) "dest_node"
            2) "3"
         5) 1) "properties"
            2) (empty list or set)
   4) 1) 1) 1) "id"
            2) "3"
         2) 1) "type"
            2) "COMPOSEDOF"
         3) 1) "src_node"
            2) "6"
         4) 1) "dest_node"
            2) "4"
         5) 1) "properties"
            2) (empty list or set)
... Etc 

I can shelf this for now and I suppose attempt the same intent of what I'd like to do here by creating factories that parse/output Cypher statements, but that doesn't really seem to be in the spirit of the library.

W-Lawless commented 1 year ago

And without opening another issue; I think it may benefit the library to implement Go generics in places such as

type Edge struct {
    ID          [uint64]
    Relation    [string]
    Source      *[Node]
    Destination *[Node]
    Properties  map[string]interface{} <----
}

I primarily work in Swift, so during the course of exploring this today I actually discovered that an interface is essentially a swift protocol The way it was being used here threw me off as admittedly I'm learning Go as I go (lol) and the way an empty interface I've typically seen used is like the 'we don't have generics so we make due' of Go, so I didn't even really understand what it was at first.

That being said, as generics types are included now, maybe you can rework some areas of the lib to use them? Go seems like they were begrudgingly reluctant to offer them at all.

Either way, perhaps I'm trying to force a Swift/OO paradigm onto Go with my above approach; perhaps there's another solution to what I'm attempting here that doesn't involve generic types at all and instead follows a more true-to-intent use of interface and functional paradigm?

W-Lawless commented 1 year ago

Turns out my code works fine 😎

The only problem is that I had my "name" label passing an integer value for my NavButton -- changing my Label value to "count" fixed everything.

Feel free to add my contribution to the documentation as example code! It's pretty slick I gotta say