diff --git a/.circleci/config.yml b/.circleci/config.yml index 28dfbb2..b3ab699 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,20 +1,33 @@ # Golang CircleCI 2.0 configuration file # # Check https://circleci.com/docs/2.0/language-go/ for more details -version: 2 +version: 2.1 +commands: + early_return_for_forked_pull_requests: + description: >- + If this build is from a fork, stop executing the current job and return success. + This is useful to avoid steps that will fail due to missing credentials. + steps: + - run: + name: Early return if this build is from a forked PR + command: | + if [ -n "$CIRCLE_PR_NUMBER" ]; then + echo "Nothing to do for forked PRs, so marking this step successful" + circleci step halt + fi jobs: build: docker: - image: circleci/golang:1.9 - image: redislabs/redisgraph:edge - port: 6379:6379 working_directory: /go/src/github.com/RedisGraph/redisgraph-go steps: - checkout - run: go get -v -t -d ./... - run: go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic + - early_return_for_forked_pull_requests - run: bash <(curl -s https://codecov.io/bash) -t ${CODECOV_TOKEN} workflows: diff --git a/README.md b/README.md index f12d1f5..ff6d4ae 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,14 @@ func main() { p_age, _ := r.Get("p.age") fmt.Printf("\nAge: %d\n", p_age) } + + // Path matching example. + query = "MATCH p = (:Person)-[:Visited]->(:Country) RETURN p" + result, _ = graph.Query(query) + res.Next() + r := res.Record() + p, ok := r.GetByIndex(0).(Path) + fmt.Printf("%s", p) } ``` diff --git a/client_test.go b/client_test.go index 78f77b7..0c7ee08 100644 --- a/client_test.go +++ b/client_test.go @@ -215,3 +215,44 @@ func TestArray(t *testing.T) { assert.Equal(t, b.GetProperty("age"), resB.GetProperty("age"), "Unexpected property value.") assert.Equal(t, b.GetProperty("array"), resB.GetProperty("array"), "Unexpected property value.") } + +func TestPath(t *testing.T) { + createGraph() + q := "MATCH p = (:Person)-[:Visited]->(:Country) RETURN p" + res, err := graph.Query(q) + if err != nil { + t.Error(err) + } + + assert.Equal(t, len(res.results), 1, "expecting 1 result record") + + res.Next() + r := res.Record() + + p, ok := r.GetByIndex(0).(Path) + assert.True(t, ok, "First column should contain path.") + + assert.Equal(t, 2, p.NodesCount(), "Path should contain two nodes") + assert.Equal(t, 1, p.EdgeCount(), "Path should contain one edge") + + s := p.FirstNode() + e := p.GetEdge(0) + d := p.LastNode() + + assert.Equal(t, s.Label, "Person", "Node should be of type 'Person'") + assert.Equal(t, e.Relation, "Visited", "Edge should be of relation type 'Visited'") + assert.Equal(t, d.Label, "Country", "Node should be of type 'Country'") + + assert.Equal(t, len(s.Properties), 4, "Person node should have 4 properties") + + assert.Equal(t, s.GetProperty("name"), "John Doe", "Unexpected property value.") + assert.Equal(t, s.GetProperty("age"), 33, "Unexpected property value.") + assert.Equal(t, s.GetProperty("gender"), "male", "Unexpected property value.") + assert.Equal(t, s.GetProperty("status"), "single", "Unexpected property value.") + + assert.Equal(t, e.GetProperty("year"), 2017, "Unexpected property value.") + + assert.Equal(t, d.GetProperty("name"), "Japan", "Unexpected property value.") + assert.Equal(t, d.GetProperty("population"), 126800000, "Unexpected property value.") + +} diff --git a/path.go b/path.go new file mode 100644 index 0000000..8144d4f --- /dev/null +++ b/path.go @@ -0,0 +1,78 @@ +package redisgraph + +import ( + "fmt" + "strings" +) + +type Path struct { + Nodes []*Node + Edges []*Edge +} + +func PathNew(nodes []interface{}, edges []interface{}) Path { + Nodes := make([]*Node, len(nodes)) + for i := 0; i < len(nodes); i++ { + Nodes[i] = nodes[i].(*Node) + } + Edges := make([]*Edge, len(edges)) + for i := 0; i < len(edges); i++ { + Edges[i] = edges[i].(*Edge) + } + + return Path{ + Edges : Edges, + Nodes : Nodes, + } +} + +func (p Path) GetNodes() []*Node { + return p.Nodes +} + +func (p Path) GetEdges() []*Edge { + return p.Edges +} + +func (p Path) GetNode(index int) *Node { + return p.Nodes[index] +} + +func (p Path) GetEdge(index int) *Edge{ + return p.Edges[index] +} + +func (p Path) FirstNode() *Node { + return p.GetNode(0) +} + +func (p Path) LastNode() *Node { + return p.GetNode(p.NodesCount() - 1) +} + +func (p Path) NodesCount() int { + return len(p.Nodes) +} + +func (p Path) EdgeCount() int { + return len(p.Edges) +} + +func (p Path) String() string { + s := []string{"<"} + edgeCount := p.EdgeCount() + for i := 0; i < edgeCount; i++ { + var node = p.GetNode(i) + s = append(s, "(" , fmt.Sprintf("%v", node.ID) , ")") + var edge = p.GetEdge(i) + if node.ID == edge.srcNodeID { + s = append(s, "-[" , fmt.Sprintf("%v", edge.ID) , "]->") + } else { + s= append(s, "<-[" , fmt.Sprintf("%v", edge.ID) , "]-") + } + } + s = append(s, "(" , fmt.Sprintf("%v", p.GetNode(edgeCount).ID) , ")") + s = append(s, ">") + + return strings.Join(s, "") +} diff --git a/query_result.go b/query_result.go index 84a376d..c8b31db 100644 --- a/query_result.go +++ b/query_result.go @@ -41,6 +41,7 @@ const ( VALUE_ARRAY VALUE_EDGE VALUE_NODE + VALUE_PATH ) type QueryResultHeader struct { @@ -217,6 +218,13 @@ func (qr *QueryResult) parseArray(cell interface{}) []interface{} { return array } +func (qr *QueryResult) parsePath(cell interface{}) Path { + arrays := cell.([]interface{}) + nodes := qr.parseScalar(arrays[0].([]interface{})) + edges := qr.parseScalar(arrays[1].([]interface{})) + return PathNew(nodes.([]interface{}), edges.([]interface{})) +} + func (qr *QueryResult) parseScalar(cell []interface{}) interface{} { t, _ := redis.Int(cell[0], nil) v := cell[1] @@ -246,6 +254,9 @@ func (qr *QueryResult) parseScalar(cell []interface{}) interface{} { case VALUE_NODE: s = qr.parseNode(v) + case VALUE_PATH: + s = qr.parsePath(v) + case VALUE_UNKNOWN: panic("Unknown scalar type\n") }