Skip to content

Commit

Permalink
shape: implement helper to walk the shape tree; resolves #651
Browse files Browse the repository at this point in the history
  • Loading branch information
dennwc committed Dec 16, 2017
1 parent b9bf6ce commit 633828c
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 1 deletion.
65 changes: 64 additions & 1 deletion graph/shape/shape.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package shape

import (
"reflect"
"regexp"

"github.com/cayleygraph/cayley/clog"
"github.com/cayleygraph/cayley/graph"
"github.com/cayleygraph/cayley/graph/iterator"
"github.com/cayleygraph/cayley/quad"
"regexp"
)

// Shape represent a query tree shape.
Expand All @@ -25,6 +27,10 @@ type Optimizer interface {
OptimizeShape(s Shape) (Shape, bool)
}

// WalkFunc is used to visit all shapes in the tree.
// If false is returned, branch will not be traversed further.
type WalkFunc func(Shape) bool

type resolveValues struct {
qs graph.QuadStore
}
Expand Down Expand Up @@ -67,6 +73,63 @@ func Optimize(s Shape, qs graph.QuadStore) (Shape, bool) {
return s, opt
}

var rtShape = reflect.TypeOf((*Shape)(nil)).Elem()

// Walk calls provided function for each shape in the tree.
func Walk(s Shape, fnc WalkFunc) {
if s == nil {
return
}
if !fnc(s) {
return
}
walkReflect(reflect.ValueOf(s), fnc)
}

func walkReflect(rv reflect.Value, fnc WalkFunc) {
rt := rv.Type()
switch rv.Kind() {
case reflect.Slice:
if rt.Elem().ConvertibleTo(rtShape) {
// all element are shapes - call function on each of them
for i := 0; i < rv.Len(); i++ {
Walk(rv.Index(i).Interface().(Shape), fnc)
}
} else {
// elements are not shapes, but might contain them
for i := 0; i < rv.Len(); i++ {
walkReflect(rv.Index(i), fnc)
}
}
case reflect.Map:
keys := rv.MapKeys()
if rt.Elem().ConvertibleTo(rtShape) {
// all element are shapes - call function on each of them
for _, k := range keys {
Walk(rv.MapIndex(k).Interface().(Shape), fnc)
}
} else {
// elements are not shapes, but might contain them
for _, k := range keys {
walkReflect(rv.MapIndex(k), fnc)
}
}
case reflect.Struct:
// visit all fields
for i := 0; i < rt.NumField(); i++ {
f := rt.Field(i)
// if field is of shape type - call function on it
// we skip anonymous fields because they were already visited as part of the parent
if !f.Anonymous && f.Type.ConvertibleTo(rtShape) {
Walk(rv.Field(i).Interface().(Shape), fnc)
continue
}
// it might be a struct/map/slice field, so we need to go deeper
walkReflect(rv.Field(i), fnc)
}
}
}

// InternalQuad is an internal representation of quad index in QuadStore.
type InternalQuad struct {
Subject graph.Value
Expand Down
33 changes: 33 additions & 0 deletions graph/shape/shape_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
package shape_test

import (
"reflect"
"testing"

"github.com/cayleygraph/cayley/graph"
"github.com/cayleygraph/cayley/graph/graphmock"
. "github.com/cayleygraph/cayley/graph/shape"
"github.com/cayleygraph/cayley/quad"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func intVal(v int) graph.Value {
Expand Down Expand Up @@ -325,3 +327,34 @@ func TestOptimize(t *testing.T) {
})
}
}

func TestWalk(t *testing.T) {
var s Shape = NodesFrom{
Dir: quad.Subject,
Quads: Quads{
{Dir: quad.Subject, Values: Fixed{intVal(1)}},
{Dir: quad.Predicate, Values: Fixed{intVal(2)}},
{
Dir: quad.Object,
Values: QuadsAction{
Result: quad.Subject,
Filter: map[quad.Direction]graph.Value{
quad.Predicate: intVal(2),
},
},
},
},
}
var types []string
Walk(s, func(s Shape) bool {
types = append(types, reflect.TypeOf(s).String())
return true
})
require.Equal(t, []string{
"shape.NodesFrom",
"shape.Quads",
"shape.Fixed",
"shape.Fixed",
"shape.QuadsAction",
}, types)
}

0 comments on commit 633828c

Please sign in to comment.