From bcbdb1f82af183a9475a50b68041017a65cb194d Mon Sep 17 00:00:00 2001 From: Barak Michener Date: Wed, 28 Oct 2015 18:35:10 -0400 Subject: [PATCH 1/5] route context through graph.Path --- graph/path/morphism_apply_functions.go | 60 +++++++++++++++++----------------- graph/path/path.go | 16 ++++++--- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/graph/path/morphism_apply_functions.go b/graph/path/morphism_apply_functions.go index b93c92b..c6b3800 100644 --- a/graph/path/morphism_apply_functions.go +++ b/graph/path/morphism_apply_functions.go @@ -37,12 +37,12 @@ func isMorphism(nodes ...string) morphism { return morphism{ Name: "is", Reversal: func() morphism { return isMorphism(nodes...) }, - Apply: func(qs graph.QuadStore, in graph.Iterator) graph.Iterator { + Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { if len(nodes) == 0 { // Acting as a passthrough here is equivalent to // building a NodesAllIterator to Next() or Contains() // from here as in previous versions. - return in + return in, ctx } isNodes := qs.FixedIterator() @@ -52,7 +52,7 @@ func isMorphism(nodes ...string) morphism { // Anything with fixedIterators will usually have a much // smaller result set, so join isNodes first here. - return join(qs, isNodes, in) + return join(qs, isNodes, in), ctx }, } } @@ -63,7 +63,7 @@ func hasMorphism(via interface{}, nodes ...string) morphism { return morphism{ Name: "has", Reversal: func() morphism { return hasMorphism(via, nodes...) }, - Apply: func(qs graph.QuadStore, in graph.Iterator) graph.Iterator { + Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { viaIter := buildViaPath(qs, via). BuildIterator() ends := func() graph.Iterator { @@ -88,13 +88,13 @@ func hasMorphism(via interface{}, nodes ...string) morphism { if len(nodes) == 0 { // Where dest involves an All iterator. route := join(qs, trail, dest) has := iterator.NewHasA(qs, route, quad.Subject) - return join(qs, in, has) + return join(qs, in, has), ctx } // This looks backwards. That's OK-- see the note above. route := join(qs, dest, trail) has := iterator.NewHasA(qs, route, quad.Subject) - return join(qs, has, in) + return join(qs, has, in), ctx }, } } @@ -103,11 +103,11 @@ func tagMorphism(tags ...string) morphism { return morphism{ Name: "tag", Reversal: func() morphism { return tagMorphism(tags...) }, - Apply: func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { + Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { for _, t := range tags { - it.Tagger().Add(t) + in.Tagger().Add(t) } - return it + return in, ctx }, tags: tags, } @@ -118,9 +118,9 @@ func outMorphism(via ...interface{}) morphism { return morphism{ Name: "out", Reversal: func() morphism { return inMorphism(via...) }, - Apply: func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { + Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { path := buildViaPath(qs, via...) - return inOutIterator(path, it, false) + return inOutIterator(path, in, false), ctx }, } } @@ -130,9 +130,9 @@ func inMorphism(via ...interface{}) morphism { return morphism{ Name: "in", Reversal: func() morphism { return outMorphism(via...) }, - Apply: func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { + Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { path := buildViaPath(qs, via...) - return inOutIterator(path, it, true) + return inOutIterator(path, in, true), ctx }, } } @@ -142,8 +142,8 @@ func iteratorMorphism(it graph.Iterator) morphism { return morphism{ Name: "iterator", Reversal: func() morphism { return iteratorMorphism(it) }, - Apply: func(qs graph.QuadStore, subIt graph.Iterator) graph.Iterator { - return join(qs, it, subIt) + Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { + return join(qs, it, in), ctx }, } } @@ -153,10 +153,10 @@ func andMorphism(p *Path) morphism { return morphism{ Name: "and", Reversal: func() morphism { return andMorphism(p) }, - Apply: func(qs graph.QuadStore, itL graph.Iterator) graph.Iterator { + Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { itR := p.BuildIteratorOn(qs) - return join(qs, itL, itR) + return join(qs, in, itR), ctx }, } } @@ -166,13 +166,13 @@ func orMorphism(p *Path) morphism { return morphism{ Name: "or", Reversal: func() morphism { return orMorphism(p) }, - Apply: func(qs graph.QuadStore, itL graph.Iterator) graph.Iterator { + Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { itR := p.BuildIteratorOn(qs) or := iterator.NewOr() - or.AddSubIterator(itL) + or.AddSubIterator(in) or.AddSubIterator(itR) - return or + return or, ctx }, } } @@ -181,8 +181,8 @@ func followMorphism(p *Path) morphism { return morphism{ Name: "follow", Reversal: func() morphism { return followMorphism(p.Reverse()) }, - Apply: func(qs graph.QuadStore, base graph.Iterator) graph.Iterator { - return p.Morphism()(qs, base) + Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { + return p.Morphism()(qs, in), ctx }, } } @@ -192,12 +192,12 @@ func exceptMorphism(p *Path) morphism { return morphism{ Name: "except", Reversal: func() morphism { return exceptMorphism(p) }, - Apply: func(qs graph.QuadStore, base graph.Iterator) graph.Iterator { - in := p.BuildIteratorOn(qs) + Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { + subIt := p.BuildIteratorOn(qs) allNodes := qs.NodesAllIterator() - notIn := iterator.NewNot(in, allNodes) + notIn := iterator.NewNot(subIt, allNodes) - return join(qs, base, notIn) + return join(qs, in, notIn), ctx }, } } @@ -206,8 +206,8 @@ func saveMorphism(via interface{}, tag string) morphism { return morphism{ Name: "save", Reversal: func() morphism { return saveMorphism(via, tag) }, - Apply: func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { - return buildSave(qs, via, tag, it, false) + Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { + return buildSave(qs, via, tag, in, false), ctx }, tags: []string{tag}, } @@ -217,8 +217,8 @@ func saveReverseMorphism(via interface{}, tag string) morphism { return morphism{ Name: "saver", Reversal: func() morphism { return saveReverseMorphism(via, tag) }, - Apply: func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { - return buildSave(qs, via, tag, it, true) + Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { + return buildSave(qs, via, tag, in, true), ctx }, tags: []string{tag}, } diff --git a/graph/path/path.go b/graph/path/path.go index 9d7b065..7fb0720 100644 --- a/graph/path/path.go +++ b/graph/path/path.go @@ -16,18 +16,25 @@ package path import "github.com/google/cayley/graph" +type applyMorphism func(graph.QuadStore, graph.Iterator, *context) (graph.Iterator, *context) + type morphism struct { Name string Reversal func() morphism - Apply graph.ApplyMorphism + Apply applyMorphism tags []string } +type context struct { + labelSet Path +} + // Path represents either a morphism (a pre-defined path stored for later use), // or a concrete path, consisting of a morphism and an underlying QuadStore. type Path struct { - stack []morphism - qs graph.QuadStore // Optionally. A nil qs is equivalent to a morphism. + stack []morphism + qs graph.QuadStore // Optionally. A nil qs is equivalent to a morphism. + baseContext *context } // IsMorphism returns whether this Path is a morphism. @@ -203,8 +210,9 @@ func (p *Path) BuildIteratorOn(qs graph.QuadStore) graph.Iterator { func (p *Path) Morphism() graph.ApplyMorphism { return func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { i := it.Clone() + ctx := p.baseContext for _, m := range p.stack { - i = m.Apply(qs, i) + i, ctx = m.Apply(qs, i, ctx) } return i } From b91b7ab8247d789c3ee0268a72a7afe1d8d0bdae Mon Sep 17 00:00:00 2001 From: Barak Michener Date: Wed, 28 Oct 2015 19:15:57 -0400 Subject: [PATCH 2/5] bring the path test in line with the gremlin test, add Back() --- data/testdata.nq | 4 +- graph/path/path.go | 50 +++++++++++++++- graph/path/path_test.go | 135 ++++++++++++++++++++++++------------------ query/gremlin/gremlin_test.go | 4 +- 4 files changed, 130 insertions(+), 63 deletions(-) diff --git a/data/testdata.nq b/data/testdata.nq index 4d211c0..12fe713 100644 --- a/data/testdata.nq +++ b/data/testdata.nq @@ -8,4 +8,6 @@ "cool_person" . . . - "cool_person" . \ No newline at end of file + "cool_person" . + . + . diff --git a/graph/path/path.go b/graph/path/path.go index 7fb0720..54506ce 100644 --- a/graph/path/path.go +++ b/graph/path/path.go @@ -14,7 +14,11 @@ package path -import "github.com/google/cayley/graph" +import ( + "fmt" + + "github.com/google/cayley/graph" +) type applyMorphism func(graph.QuadStore, graph.Iterator, *context) (graph.Iterator, *context) @@ -94,6 +98,11 @@ func (p *Path) Tag(tags ...string) *Path { return p } +// As is a synonym for Tag. +func (p *Path) As(tags ...string) *Path { + return p.Tag(tags...) +} + // Out updates this Path to represent the nodes that are adjacent to the // current nodes, via the given outbound predicate. // @@ -129,7 +138,7 @@ func (p *Path) And(path *Path) *Path { return p } -// And updates the current Path to represent the nodes that match either the +// Or updates the current Path to represent the nodes that match either the // current Path so far, or the given Path. func (p *Path) Or(path *Path) *Path { p.stack = append(p.stack, orMorphism(path)) @@ -187,6 +196,34 @@ func (p *Path) Has(via interface{}, nodes ...string) *Path { return p } +// Back returns to a previously tagged place in the path. Any constraints applied after the Tag will remain in effect, but traversal continues from the tagged point instead, not from the end of the chain. +// +// For example: +// // Will return "bob" iff "bob" is cool +// StartPath(qs, "bob").Tag("person_tag").Out("status").Is("cool").Back("person_tag") +func (p *Path) Back(tag string) *Path { + newPath := NewPath(p.qs) + i := len(p.stack) - 1 + + for { + if i < 0 { + return p.Reverse() + } + if p.stack[i].Name == "tag" { + for _, x := range p.stack[i].tags { + if x == tag { + // Found what we're looking for. + p.stack = p.stack[:i+1] + return p.And(newPath) + } + } + } + newPath.stack = append(newPath.stack, p.stack[i].Reversal()) + i-- + } + +} + // BuildIterator returns an iterator from this given Path. Note that you must // call this with a full path (not a morphism), since a morphism does not have // the ability to fetch the underlying quads. This function will panic if @@ -217,3 +254,12 @@ func (p *Path) Morphism() graph.ApplyMorphism { return i } } + +func (p *Path) debugPrint() { + var strs []string + for _, x := range p.stack { + strs = append(strs, x.Name) + } + fmt.Println("stack:", strs) + fmt.Println("ctx:", p.baseContext) +} diff --git a/graph/path/path_test.go b/graph/path/path_test.go index 6fb3362..bf22c16 100644 --- a/graph/path/path_test.go +++ b/graph/path/path_test.go @@ -15,12 +15,15 @@ package path import ( + "io" + "os" "reflect" "sort" "testing" "github.com/google/cayley/graph" "github.com/google/cayley/quad" + "github.com/google/cayley/quad/cquads" _ "github.com/google/cayley/graph/memstore" _ "github.com/google/cayley/writer" @@ -28,38 +31,43 @@ import ( // This is a simple test graph. // -// +---+ +---+ -// | A |------- ->| F |<-- -// +---+ \------>+---+-/ +---+ \--+---+ -// ------>|#B#| | | E | -// +---+-------/ >+---+ | +---+ -// | C | / v -// +---+ -/ +---+ -// ---- +---+/ |#G#| -// \-->|#D#|------------->+---+ -// +---+ -// +// +-------+ +------+ +// | alice |----- ->| fred |<-- +// +-------+ \---->+-------+-/ +------+ \-+-------+ +// ----->| #bob# | | | emily | +// +---------+--/ --->+-------+ | +-------+ +// | charlie | / v +// +---------+ / +--------+ +// \--- +--------+ | #greg# | +// \-->| #dani# |------------>+--------+ +// +--------+ -var simpleGraph = []quad.Quad{ - {"A", "follows", "B", ""}, - {"C", "follows", "B", ""}, - {"C", "follows", "D", ""}, - {"D", "follows", "B", ""}, - {"B", "follows", "F", ""}, - {"F", "follows", "G", ""}, - {"D", "follows", "G", ""}, - {"E", "follows", "F", ""}, - {"B", "status", "cool", "status_graph"}, - {"D", "status", "cool", "status_graph"}, - {"G", "status", "cool", "status_graph"}, - {"predicates", "are", "follows", ""}, - {"predicates", "are", "status", ""}, +func loadGraph(path string, t testing.TB) []quad.Quad { + var r io.Reader + var simpleGraph []quad.Quad + f, err := os.Open(path) + if err != nil { + t.Fatalf("Failed to open %q: %v", path, err) + } + defer f.Close() + r = f + + dec := cquads.NewDecoder(r) + q1, err := dec.Unmarshal() + if err != nil { + t.Fatalf("Failed to Unmarshal: %v", err) + } + for ; err == nil; q1, err = dec.Unmarshal() { + simpleGraph = append(simpleGraph, q1) + } + return simpleGraph } -func makeTestStore(data []quad.Quad) graph.QuadStore { +func makeTestStore(t testing.TB) graph.QuadStore { + simpleGraph := loadGraph("../../data/testdata.nq", t) qs, _ := graph.NewQuadStore("memstore", "", nil) w, _ := graph.NewQuadWriter("single", qs, nil) - for _, t := range data { + for _, t := range simpleGraph { w.AddQuad(t) } return qs @@ -104,89 +112,100 @@ func testSet(qs graph.QuadStore) []test { return []test{ { message: "use out", - path: StartPath(qs, "A").Out("follows"), - expect: []string{"B"}, + path: StartPath(qs, "alice").Out("follows"), + expect: []string{"bob"}, }, { message: "use in", - path: StartPath(qs, "B").In("follows"), - expect: []string{"A", "C", "D"}, + path: StartPath(qs, "bob").In("follows"), + expect: []string{"alice", "charlie", "dani"}, }, { message: "use path Out", - path: StartPath(qs, "B").Out(StartPath(qs, "predicates").Out("are")), - expect: []string{"F", "cool"}, + path: StartPath(qs, "bob").Out(StartPath(qs, "predicates").Out("are")), + expect: []string{"fred", "cool_person"}, }, { message: "use And", - path: StartPath(qs, "D").Out("follows").And( - StartPath(qs, "C").Out("follows")), - expect: []string{"B"}, + path: StartPath(qs, "dani").Out("follows").And( + StartPath(qs, "charlie").Out("follows")), + expect: []string{"bob"}, }, { message: "use Or", - path: StartPath(qs, "F").Out("follows").Or( - StartPath(qs, "A").Out("follows")), - expect: []string{"B", "G"}, + path: StartPath(qs, "fred").Out("follows").Or( + StartPath(qs, "alice").Out("follows")), + expect: []string{"bob", "greg"}, }, { message: "implicit All", path: StartPath(qs), - expect: []string{"A", "B", "C", "D", "E", "F", "G", "follows", "status", "cool", "status_graph", "predicates", "are"}, + expect: []string{"alice", "bob", "charlie", "dani", "emily", "fred", "greg", "follows", "status", "cool_person", "predicates", "are"}, }, { message: "follow", - path: StartPath(qs, "C").Follow(StartMorphism().Out("follows").Out("follows")), - expect: []string{"B", "F", "G"}, + path: StartPath(qs, "charlie").Follow(StartMorphism().Out("follows").Out("follows")), + expect: []string{"bob", "fred", "greg"}, }, { message: "followR", - path: StartPath(qs, "F").FollowReverse(StartMorphism().Out("follows").Out("follows")), - expect: []string{"A", "C", "D"}, + path: StartPath(qs, "fred").FollowReverse(StartMorphism().Out("follows").Out("follows")), + expect: []string{"alice", "charlie", "dani"}, }, { message: "is, tag, instead of FollowR", - path: StartPath(qs).Tag("first").Follow(StartMorphism().Out("follows").Out("follows")).Is("F"), - expect: []string{"A", "C", "D"}, + path: StartPath(qs).Tag("first").Follow(StartMorphism().Out("follows").Out("follows")).Is("fred"), + expect: []string{"alice", "charlie", "dani"}, tag: "first", }, { message: "use Except to filter out a single vertex", - path: StartPath(qs, "A", "B").Except(StartPath(qs, "A")), - expect: []string{"B"}, + path: StartPath(qs, "alice", "bob").Except(StartPath(qs, "alice")), + expect: []string{"bob"}, }, { message: "use chained Except", - path: StartPath(qs, "A", "B", "C").Except(StartPath(qs, "B")).Except(StartPath(qs, "A")), - expect: []string{"C"}, + path: StartPath(qs, "alice", "bob", "charlie").Except(StartPath(qs, "bob")).Except(StartPath(qs, "alice")), + expect: []string{"charlie"}, }, { message: "show a simple save", path: StartPath(qs).Save("status", "somecool"), tag: "somecool", - expect: []string{"cool", "cool", "cool"}, + expect: []string{"cool_person", "cool_person", "cool_person"}, }, { message: "show a simple saveR", - path: StartPath(qs, "cool").SaveReverse("status", "who"), + path: StartPath(qs, "cool_person").SaveReverse("status", "who"), tag: "who", - expect: []string{"G", "D", "B"}, + expect: []string{"greg", "dani", "bob"}, }, { message: "show a simple Has", - path: StartPath(qs).Has("status", "cool"), - expect: []string{"G", "D", "B"}, + path: StartPath(qs).Has("status", "cool_person"), + expect: []string{"greg", "dani", "bob"}, }, { message: "show a double Has", - path: StartPath(qs).Has("status", "cool").Has("follows", "F"), - expect: []string{"B"}, + path: StartPath(qs).Has("status", "cool_person").Has("follows", "fred"), + expect: []string{"bob"}, + }, + { + message: "use .Tag()-.Is()-.Back()", + path: StartPath(qs, "bob").In("follows").Tag("foo").Out("status").Is("cool_person").Back("foo"), + expect: []string{"dani"}, + }, + { + message: "do multiple .Back()s", + path: StartPath(qs, "emily").Out("follows").As("f").Out("follows").Out("status").Is("cool_person").Back("f").In("follows").In("follows").As("acd").Out("status").Is("cool_person").Back("f"), + tag: "acd", + expect: []string{"dani"}, }, } } func TestMorphisms(t *testing.T) { - qs := makeTestStore(simpleGraph) + qs := makeTestStore(t) for _, test := range testSet(qs) { var got []string if test.tag == "" { diff --git a/query/gremlin/gremlin_test.go b/query/gremlin/gremlin_test.go index 5c86b8f..55afd6a 100644 --- a/query/gremlin/gremlin_test.go +++ b/query/gremlin/gremlin_test.go @@ -262,14 +262,14 @@ var testQueries = []struct { query: ` g.V().InPredicates().All() `, - expect: []string{"follows", "status"}, + expect: []string{"are", "follows", "status"}, }, { message: "list all out predicates", query: ` g.V().OutPredicates().All() `, - expect: []string{"follows", "status"}, + expect: []string{"are", "follows", "status"}, }, } From 97247ae40ff3406e25ba5e5cc9a6decfaff56ca0 Mon Sep 17 00:00:00 2001 From: Barak Michener Date: Wed, 28 Oct 2015 19:42:59 -0400 Subject: [PATCH 3/5] add InPredicates/OutPredicates --- graph/path/morphism_apply_functions.go | 22 ++++++++++++++++++++++ graph/path/path.go | 33 +++++++++++++++++++++++++++------ graph/path/path_test.go | 29 ++++++++++++++++++++++++++++- 3 files changed, 77 insertions(+), 7 deletions(-) diff --git a/graph/path/morphism_apply_functions.go b/graph/path/morphism_apply_functions.go index c6b3800..e4160e6 100644 --- a/graph/path/morphism_apply_functions.go +++ b/graph/path/morphism_apply_functions.go @@ -137,6 +137,28 @@ func inMorphism(via ...interface{}) morphism { } } +// predicatesMorphism iterates to the uniqified set of predicates from +// the given set of nodes in the path. +func predicatesMorphism(isIn bool) morphism { + m := morphism{ + Name: "out_predicates", + Reversal: func() morphism { panic("not implemented: need a function from predicates to their associated edges") }, + Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { + dir := quad.Subject + if isIn { + dir = quad.Object + } + lto := iterator.NewLinksTo(qs, in, dir) + hasa := iterator.NewHasA(qs, lto, quad.Predicate) + return iterator.NewUnique(hasa), ctx + }, + } + if isIn { + m.Name = "in_predicates" + } + return m +} + // iteratorMorphism simply tacks the input iterator onto the chain. func iteratorMorphism(it graph.Iterator) morphism { return morphism{ diff --git a/graph/path/path.go b/graph/path/path.go index 54506ce..be7e8fd 100644 --- a/graph/path/path.go +++ b/graph/path/path.go @@ -98,11 +98,6 @@ func (p *Path) Tag(tags ...string) *Path { return p } -// As is a synonym for Tag. -func (p *Path) As(tags ...string) *Path { - return p.Tag(tags...) -} - // Out updates this Path to represent the nodes that are adjacent to the // current nodes, via the given outbound predicate. // @@ -131,6 +126,33 @@ func (p *Path) In(via ...interface{}) *Path { return p } +// InPredicates updates this path to represent the nodes of the valid inbound +// predicates from the current nodes. +// +// For example: +// // Returns a list of predicates valid from "bob" +// // +// // Will return []string{"follows"} if there are any things that "follow" Bob +// StartPath(qs, "bob").InPredicates() +func (p *Path) InPredicates() *Path { + p.stack = append(p.stack, predicatesMorphism(true)) + return p +} + +// OutPredicates updates this path to represent the nodes of the valid inbound +// predicates from the current nodes. +// +// For example: +// // Returns a list of predicates valid from "bob" +// // +// // Will return []string{"follows", "status"} if there are edges from "bob" +// // labelled "follows", and edges from "bob" that describe his "status". +// StartPath(qs, "bob").OutPredicates() +func (p *Path) OutPredicates() *Path { + p.stack = append(p.stack, predicatesMorphism(false)) + return p +} + // And updates the current Path to represent the nodes that match both the // current Path so far, and the given Path. func (p *Path) And(path *Path) *Path { @@ -221,7 +243,6 @@ func (p *Path) Back(tag string) *Path { newPath.stack = append(newPath.stack, p.stack[i].Reversal()) i-- } - } // BuildIterator returns an iterator from this given Path. Note that you must diff --git a/graph/path/path_test.go b/graph/path/path_test.go index bf22c16..ecae42d 100644 --- a/graph/path/path_test.go +++ b/graph/path/path_test.go @@ -108,6 +108,12 @@ type test struct { tag string } +// Define morphisms without a QuadStore + +var ( + grandfollows = StartMorphism().Out("follows").Out("follows") +) + func testSet(qs graph.QuadStore) []test { return []test{ { @@ -197,10 +203,31 @@ func testSet(qs graph.QuadStore) []test { }, { message: "do multiple .Back()s", - path: StartPath(qs, "emily").Out("follows").As("f").Out("follows").Out("status").Is("cool_person").Back("f").In("follows").In("follows").As("acd").Out("status").Is("cool_person").Back("f"), + path: StartPath(qs, "emily").Out("follows").Tag("f").Out("follows").Out("status").Is("cool_person").Back("f").In("follows").In("follows").Tag("acd").Out("status").Is("cool_person").Back("f"), tag: "acd", expect: []string{"dani"}, }, + { + message: "InPredicates()", + path: StartPath(qs, "bob").InPredicates(), + expect: []string{"follows"}, + }, + { + message: "OutPredicates()", + path: StartPath(qs, "bob").OutPredicates(), + expect: []string{"follows", "status"}, + }, + // Morphism tests + { + message: "show simple morphism", + path: StartPath(qs, "charlie").Follow(grandfollows), + expect: []string{"greg", "fred", "bob"}, + }, + { + message: "show reverse morphism", + path: StartPath(qs, "fred").FollowReverse(grandfollows), + expect: []string{"alice", "charlie", "dani"}, + }, } } From f74051a5204642cfc6d30b5d6af1dd15d64c4e42 Mon Sep 17 00:00:00 2001 From: Barak Michener Date: Wed, 28 Oct 2015 21:26:29 -0400 Subject: [PATCH 4/5] Rewrite Gremlin's buildIterator in terms of paths --- graph/path/morphism_apply_functions.go | 47 +++- graph/path/path.go | 52 +++-- query/gremlin/build_iterator.go | 380 +++++++++++++-------------------- query/gremlin/gremlin_test.go | 1 + query/gremlin/traversals.go | 157 +++----------- 5 files changed, 246 insertions(+), 391 deletions(-) diff --git a/graph/path/morphism_apply_functions.go b/graph/path/morphism_apply_functions.go index e4160e6..1339762 100644 --- a/graph/path/morphism_apply_functions.go +++ b/graph/path/morphism_apply_functions.go @@ -15,6 +15,8 @@ package path import ( + "fmt" + "github.com/google/cayley/graph" "github.com/google/cayley/graph/iterator" "github.com/google/cayley/quad" @@ -114,25 +116,41 @@ func tagMorphism(tags ...string) morphism { } // outMorphism iterates forward one RDF triple or via an entire path. -func outMorphism(via ...interface{}) morphism { +func outMorphism(tags []string, via ...interface{}) morphism { return morphism{ Name: "out", - Reversal: func() morphism { return inMorphism(via...) }, + Reversal: func() morphism { return inMorphism(tags, via...) }, Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { path := buildViaPath(qs, via...) - return inOutIterator(path, in, false), ctx + return inOutIterator(path, in, false, tags), ctx }, } } // inMorphism iterates backwards one RDF triple or via an entire path. -func inMorphism(via ...interface{}) morphism { +func inMorphism(tags []string, via ...interface{}) morphism { return morphism{ Name: "in", - Reversal: func() morphism { return outMorphism(via...) }, + Reversal: func() morphism { return outMorphism(tags, via...) }, Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { path := buildViaPath(qs, via...) - return inOutIterator(path, in, true), ctx + return inOutIterator(path, in, true, tags), ctx + }, + } +} + +func bothMorphism(tags []string, via ...interface{}) morphism { + return morphism{ + Name: "in", + Reversal: func() morphism { return bothMorphism(tags, via...) }, + Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { + path := buildViaPath(qs, via...) + inSide := inOutIterator(path, in, true, tags) + outSide := inOutIterator(path, in.Clone(), false, tags) + or := iterator.NewOr() + or.AddSubIterator(inSide) + or.AddSubIterator(outSide) + return or, ctx }, } } @@ -270,13 +288,16 @@ func buildSave( return join(qs, from, save) } -func inOutIterator(viaPath *Path, from graph.Iterator, inIterator bool) graph.Iterator { +func inOutIterator(viaPath *Path, from graph.Iterator, inIterator bool, tags []string) graph.Iterator { start, goal := quad.Subject, quad.Object if inIterator { start, goal = goal, start } viaIter := viaPath.BuildIterator() + for _, tag := range tags { + viaIter.Tagger().Add(tag) + } source := iterator.NewLinksTo(viaPath.qs, from, start) trail := iterator.NewLinksTo(viaPath.qs, viaIter, quad.Predicate) @@ -292,12 +313,22 @@ func buildViaPath(qs graph.QuadStore, via ...interface{}) *Path { } else if len(via) == 1 { v := via[0] switch p := v.(type) { + case nil: + return PathFromIterator(qs, qs.NodesAllIterator()) case *Path: + if p.qs != qs { + newp := &Path{ + qs: qs, + baseContext: p.baseContext, + stack: p.stack[:], + } + return newp + } return p case string: return StartPath(qs, p) default: - panic("Invalid type passed to buildViaPath.") + panic(fmt.Sprint("Invalid type passed to buildViaPath. ", p)) } } var strings []string diff --git a/graph/path/path.go b/graph/path/path.go index be7e8fd..61f1d45 100644 --- a/graph/path/path.go +++ b/graph/path/path.go @@ -14,11 +14,7 @@ package path -import ( - "fmt" - - "github.com/google/cayley/graph" -) +import "github.com/google/cayley/graph" type applyMorphism func(graph.QuadStore, graph.Iterator, *context) (graph.Iterator, *context) @@ -102,13 +98,13 @@ func (p *Path) Tag(tags ...string) *Path { // current nodes, via the given outbound predicate. // // For example: -// // Returns the list of nodes that "A" follows. +// // Returns the list of nodes that "B" follows. // // -// // Will return []string{"B"} if there is a predicate (edge) from "A" -// // to "B" labelled "follows". +// // Will return []string{"F"} if there is a predicate (edge) from "B" +// // to "F" labelled "follows". // StartPath(qs, "A").Out("follows") func (p *Path) Out(via ...interface{}) *Path { - p.stack = append(p.stack, outMorphism(via...)) + p.stack = append(p.stack, outMorphism(nil, via...)) return p } @@ -122,7 +118,34 @@ func (p *Path) Out(via ...interface{}) *Path { // // edges from those nodes to "B" labelled "follows". // StartPath(qs, "B").In("follows") func (p *Path) In(via ...interface{}) *Path { - p.stack = append(p.stack, inMorphism(via...)) + p.stack = append(p.stack, inMorphism(nil, via...)) + return p +} + +// InWithTags is exactly like In, except it tags the value of the predicate +// traversed with the tags provided. +func (p *Path) InWithTags(tags []string, via ...interface{}) *Path { + p.stack = append(p.stack, inMorphism(tags, via...)) + return p +} + +// OutWithTags is exactly like In, except it tags the value of the predicate +// traversed with the tags provided. +func (p *Path) OutWithTags(tags []string, via ...interface{}) *Path { + p.stack = append(p.stack, outMorphism(tags, via...)) + return p +} + +// Both updates this path following both inbound and outbound predicates. +// +// For example: +// // Return the list of nodes that follow or are followed by "B". +// // +// // Will return []string{"A", "C", "D", "F} if there are the appropriate +// // edges from those nodes to "B" labelled "follows", in either direction. +// StartPath(qs, "B").Both("follows") +func (p *Path) Both(via ...interface{}) *Path { + p.stack = append(p.stack, bothMorphism(nil, via...)) return p } @@ -275,12 +298,3 @@ func (p *Path) Morphism() graph.ApplyMorphism { return i } } - -func (p *Path) debugPrint() { - var strs []string - for _, x := range p.stack { - strs = append(strs, x.Name) - } - fmt.Println("stack:", strs) - fmt.Println("ctx:", p.baseContext) -} diff --git a/query/gremlin/build_iterator.go b/query/gremlin/build_iterator.go index 1fd3b40..95a8652 100644 --- a/query/gremlin/build_iterator.go +++ b/query/gremlin/build_iterator.go @@ -15,6 +15,7 @@ package gremlin import ( + "fmt" "strconv" "github.com/barakmich/glog" @@ -22,7 +23,7 @@ import ( "github.com/google/cayley/graph" "github.com/google/cayley/graph/iterator" - "github.com/google/cayley/quad" + "github.com/google/cayley/graph/path" ) func propertiesOf(obj *otto.Object, name string) []string { @@ -38,7 +39,133 @@ func buildIteratorTree(obj *otto.Object, qs graph.QuadStore) graph.Iterator { if !isVertexChain(obj) { return iterator.NewNull() } - return buildIteratorTreeHelper(obj, qs, iterator.NewNull()) + path := buildPathFromObject(obj) + if path == nil { + return iterator.NewNull() + } + return path.BuildIteratorOn(qs) +} + +func getFirstArgAsVertexChain(obj *otto.Object) *otto.Object { + arg, _ := obj.Get("_gremlin_values") + firstArg, _ := arg.Object().Get("0") + if !isVertexChain(firstArg.Object()) { + return nil + } + return firstArg.Object() +} + +func getFirstArgAsMorphismChain(obj *otto.Object) *otto.Object { + arg, _ := obj.Get("_gremlin_values") + firstArg, _ := arg.Object().Get("0") + if isVertexChain(firstArg.Object()) { + return nil + } + return firstArg.Object() +} + +func buildPathFromObject(obj *otto.Object) *path.Path { + var p *path.Path + val, _ := obj.Get("_gremlin_type") + stringArgs := propertiesOf(obj, "string_args") + gremlinType := val.String() + if prev, _ := obj.Get("_gremlin_prev"); !prev.IsObject() { + switch gremlinType { + case "vertex": + return path.StartMorphism(stringArgs...) + case "morphism": + return path.StartMorphism() + default: + panic("No base gremlin path other than 'vertex' or 'morphism'") + } + } else { + p = buildPathFromObject(prev.Object()) + } + if p == nil { + return nil + } + switch gremlinType { + case "Is": + return p.Is(stringArgs...) + case "In": + preds, tags, ok := getViaData(obj) + if !ok { + return nil + } + return p.InWithTags(tags, preds...) + case "Out": + preds, tags, ok := getViaData(obj) + if !ok { + return nil + } + return p.OutWithTags(tags, preds...) + case "Both": + preds, _, ok := getViaData(obj) + if !ok { + return nil + } + return p.Both(preds...) + case "Follow": + subobj := getFirstArgAsMorphismChain(obj) + if subobj == nil { + return nil + } + return p.Follow(buildPathFromObject(subobj)) + case "FollowR": + subobj := getFirstArgAsMorphismChain(obj) + if subobj == nil { + return nil + } + return p.FollowReverse(buildPathFromObject(subobj)) + case "And", "Intersect": + subobj := getFirstArgAsVertexChain(obj) + if subobj == nil { + return nil + } + return p.And(buildPathFromObject(subobj)) + case "Union", "Or": + subobj := getFirstArgAsVertexChain(obj) + if subobj == nil { + return nil + } + return p.And(buildPathFromObject(subobj)) + case "Back": + if len(stringArgs) != 1 { + return nil + } + return p.Back(stringArgs[0]) + case "Tag", "As": + return p.Tag(stringArgs...) + case "Has": + if len(stringArgs) < 2 { + return nil + } + return p.Has(stringArgs[0], stringArgs[1:]...) + case "Save", "SaveR": + if len(stringArgs) > 2 || len(stringArgs) == 0 { + return nil + } + tag := stringArgs[0] + if len(stringArgs) == 2 { + tag = stringArgs[1] + } + if gremlinType == "SaveR" { + return p.SaveReverse(stringArgs[0], tag) + } + return p.Save(stringArgs[0], tag) + case "Except", "Difference": + subobj := getFirstArgAsVertexChain(obj) + if subobj == nil { + return nil + } + return p.Except(buildPathFromObject(subobj)) + case "InPredicates": + return p.InPredicates() + case "OutPredicates": + return p.OutPredicates() + default: + panic(fmt.Sprint("Unimplemented Gremlin function", gremlinType)) + } } func stringsFrom(obj *otto.Object) []string { @@ -57,32 +184,30 @@ func stringsFrom(obj *otto.Object) []string { return output } -func buildIteratorFromValue(val otto.Value, qs graph.QuadStore) graph.Iterator { +func buildPathFromValue(val otto.Value) (out []interface{}) { if val.IsNull() || val.IsUndefined() { - return qs.NodesAllIterator() + return nil } if val.IsPrimitive() { thing, _ := val.Export() switch v := thing.(type) { case string: - it := qs.FixedIterator() - it.Add(qs.ValueOf(v)) - return it + out = append(out, v) + return default: glog.Errorln("Trying to build unknown primitive value.") } } switch val.Class() { case "Object": - return buildIteratorTree(val.Object(), qs) + out = append(out, buildPathFromObject(val.Object())) + return case "Array": // Had better be an array of strings - strings := stringsFrom(val.Object()) - it := qs.FixedIterator() - for _, x := range strings { - it.Add(qs.ValueOf(x)) + for _, x := range stringsFrom(val.Object()) { + out = append(out, x) } - return it + return case "Number": fallthrough case "Boolean": @@ -90,248 +215,37 @@ func buildIteratorFromValue(val otto.Value, qs graph.QuadStore) graph.Iterator { case "Date": fallthrough case "String": - it := qs.FixedIterator() - it.Add(qs.ValueOf(val.String())) - return it + out = append(out, val.String()) + return default: glog.Errorln("Trying to handle unsupported Javascript value.") - return iterator.NewNull() + return nil } } -func buildInOutIterator(obj *otto.Object, qs graph.QuadStore, base graph.Iterator, isReverse bool) graph.Iterator { +func getViaData(obj *otto.Object) (predicates []interface{}, tags []string, ok bool) { argList, _ := obj.Get("_gremlin_values") if argList.Class() != "GoArray" { glog.Errorln("How is arglist not an array? Return nothing.", argList.Class()) - return iterator.NewNull() + return nil, nil, false } argArray := argList.Object() lengthVal, _ := argArray.Get("length") length, _ := lengthVal.ToInteger() - var predicateNodeIterator graph.Iterator if length == 0 { - predicateNodeIterator = qs.NodesAllIterator() + predicates = []interface{}{} } else { zero, _ := argArray.Get("0") - predicateNodeIterator = buildIteratorFromValue(zero, qs) + predicates = buildPathFromValue(zero) } if length >= 2 { - var tags []string one, _ := argArray.Get("1") if one.IsString() { tags = append(tags, one.String()) } else if one.Class() == "Array" { tags = stringsFrom(one.Object()) } - for _, tag := range tags { - predicateNodeIterator.Tagger().Add(tag) - } } - - in, out := quad.Subject, quad.Object - if isReverse { - in, out = out, in - } - lto := iterator.NewLinksTo(qs, base, in) - and := iterator.NewAnd(qs) - and.AddSubIterator(iterator.NewLinksTo(qs, predicateNodeIterator, quad.Predicate)) - and.AddSubIterator(lto) - return iterator.NewHasA(qs, and, out) -} - -func buildInOutPredicateIterator(obj *otto.Object, qs graph.QuadStore, base graph.Iterator, isReverse bool) graph.Iterator { - dir := quad.Subject - if isReverse { - dir = quad.Object - } - lto := iterator.NewLinksTo(qs, base, dir) - hasa := iterator.NewHasA(qs, lto, quad.Predicate) - return iterator.NewUnique(hasa) -} - -func buildIteratorTreeHelper(obj *otto.Object, qs graph.QuadStore, base graph.Iterator) graph.Iterator { - // TODO: Better error handling - var ( - it graph.Iterator - subIt graph.Iterator - ) - - if prev, _ := obj.Get("_gremlin_prev"); !prev.IsObject() { - subIt = base - } else { - subIt = buildIteratorTreeHelper(prev.Object(), qs, base) - } - - stringArgs := propertiesOf(obj, "string_args") - val, _ := obj.Get("_gremlin_type") - switch val.String() { - case "vertex": - if len(stringArgs) == 0 { - it = qs.NodesAllIterator() - } else { - fixed := qs.FixedIterator() - for _, name := range stringArgs { - fixed.Add(qs.ValueOf(name)) - } - it = fixed - } - case "tag": - it = subIt - for _, tag := range stringArgs { - it.Tagger().Add(tag) - } - case "save": - all := qs.NodesAllIterator() - if len(stringArgs) > 2 || len(stringArgs) == 0 { - return iterator.NewNull() - } - if len(stringArgs) == 2 { - all.Tagger().Add(stringArgs[1]) - } else { - all.Tagger().Add(stringArgs[0]) - } - predFixed := qs.FixedIterator() - predFixed.Add(qs.ValueOf(stringArgs[0])) - subAnd := iterator.NewAnd(qs) - subAnd.AddSubIterator(iterator.NewLinksTo(qs, predFixed, quad.Predicate)) - subAnd.AddSubIterator(iterator.NewLinksTo(qs, all, quad.Object)) - hasa := iterator.NewHasA(qs, subAnd, quad.Subject) - and := iterator.NewAnd(qs) - and.AddSubIterator(hasa) - and.AddSubIterator(subIt) - it = and - case "saver": - all := qs.NodesAllIterator() - if len(stringArgs) > 2 || len(stringArgs) == 0 { - return iterator.NewNull() - } - if len(stringArgs) == 2 { - all.Tagger().Add(stringArgs[1]) - } else { - all.Tagger().Add(stringArgs[0]) - } - predFixed := qs.FixedIterator() - predFixed.Add(qs.ValueOf(stringArgs[0])) - subAnd := iterator.NewAnd(qs) - subAnd.AddSubIterator(iterator.NewLinksTo(qs, predFixed, quad.Predicate)) - subAnd.AddSubIterator(iterator.NewLinksTo(qs, all, quad.Subject)) - hasa := iterator.NewHasA(qs, subAnd, quad.Object) - and := iterator.NewAnd(qs) - and.AddSubIterator(hasa) - and.AddSubIterator(subIt) - it = and - case "has": - fixed := qs.FixedIterator() - if len(stringArgs) < 2 { - return iterator.NewNull() - } - for _, name := range stringArgs[1:] { - fixed.Add(qs.ValueOf(name)) - } - predFixed := qs.FixedIterator() - predFixed.Add(qs.ValueOf(stringArgs[0])) - subAnd := iterator.NewAnd(qs) - subAnd.AddSubIterator(iterator.NewLinksTo(qs, predFixed, quad.Predicate)) - subAnd.AddSubIterator(iterator.NewLinksTo(qs, fixed, quad.Object)) - hasa := iterator.NewHasA(qs, subAnd, quad.Subject) - and := iterator.NewAnd(qs) - and.AddSubIterator(hasa) - and.AddSubIterator(subIt) - it = and - case "morphism": - it = base - case "and": - arg, _ := obj.Get("_gremlin_values") - firstArg, _ := arg.Object().Get("0") - if !isVertexChain(firstArg.Object()) { - return iterator.NewNull() - } - argIt := buildIteratorTree(firstArg.Object(), qs) - - and := iterator.NewAnd(qs) - and.AddSubIterator(subIt) - and.AddSubIterator(argIt) - it = and - case "back": - arg, _ := obj.Get("_gremlin_back_chain") - argIt := buildIteratorTree(arg.Object(), qs) - and := iterator.NewAnd(qs) - and.AddSubIterator(subIt) - and.AddSubIterator(argIt) - it = and - case "is": - fixed := qs.FixedIterator() - for _, name := range stringArgs { - fixed.Add(qs.ValueOf(name)) - } - and := iterator.NewAnd(qs) - and.AddSubIterator(fixed) - and.AddSubIterator(subIt) - it = and - case "or": - arg, _ := obj.Get("_gremlin_values") - firstArg, _ := arg.Object().Get("0") - if !isVertexChain(firstArg.Object()) { - return iterator.NewNull() - } - argIt := buildIteratorTree(firstArg.Object(), qs) - - or := iterator.NewOr() - or.AddSubIterator(subIt) - or.AddSubIterator(argIt) - it = or - case "both": - // Hardly the most efficient pattern, but the most general. - // Worth looking into an Optimize() optimization here. - clone := subIt.Clone() - it1 := buildInOutIterator(obj, qs, subIt, false) - it2 := buildInOutIterator(obj, qs, clone, true) - - or := iterator.NewOr() - or.AddSubIterator(it1) - or.AddSubIterator(it2) - it = or - case "out": - it = buildInOutIterator(obj, qs, subIt, false) - case "follow": - // Follow a morphism - arg, _ := obj.Get("_gremlin_values") - firstArg, _ := arg.Object().Get("0") - if isVertexChain(firstArg.Object()) { - return iterator.NewNull() - } - it = buildIteratorTreeHelper(firstArg.Object(), qs, subIt) - case "followr": - // Follow a morphism - arg, _ := obj.Get("_gremlin_followr") - if isVertexChain(arg.Object()) { - return iterator.NewNull() - } - it = buildIteratorTreeHelper(arg.Object(), qs, subIt) - case "in": - it = buildInOutIterator(obj, qs, subIt, true) - case "except": - arg, _ := obj.Get("_gremlin_values") - firstArg, _ := arg.Object().Get("0") - if !isVertexChain(firstArg.Object()) { - return iterator.NewNull() - } - - allIt := qs.NodesAllIterator() - toComplementIt := buildIteratorTree(firstArg.Object(), qs) - notIt := iterator.NewNot(toComplementIt, allIt) - - and := iterator.NewAnd(qs) - and.AddSubIterator(subIt) - and.AddSubIterator(notIt) - it = and - case "in_predicates": - it = buildInOutPredicateIterator(obj, qs, subIt, true) - case "out_predicates": - it = buildInOutPredicateIterator(obj, qs, subIt, false) - } - if it == nil { - panic("Iterator building does not catch the output iterator in some case.") - } - return it + ok = true + return } diff --git a/query/gremlin/gremlin_test.go b/query/gremlin/gremlin_test.go index 55afd6a..3ae992d 100644 --- a/query/gremlin/gremlin_test.go +++ b/query/gremlin/gremlin_test.go @@ -322,6 +322,7 @@ func TestGremlin(t *testing.T) { got := runQueryGetTag(simpleGraph, test.query, test.tag) sort.Strings(got) sort.Strings(test.expect) + t.Log("testing", test.message) if !reflect.DeepEqual(got, test.expect) { t.Errorf("Failed to %s, got: %v expected: %v", test.message, got, test.expect) } diff --git a/query/gremlin/traversals.go b/query/gremlin/traversals.go index 8eee050..b5304e4 100644 --- a/query/gremlin/traversals.go +++ b/query/gremlin/traversals.go @@ -21,27 +21,33 @@ import ( "github.com/robertkrimen/otto" ) +var traversals = []string{ + "In", + "Out", + "Is", + "Both", + "Follow", + "FollowR", + "And", + "Intersect", + "Union", + "Or", + "Back", + "Tag", + "As", + "Has", + "Save", + "SaveR", + "Except", + "Difference", + "InPredicates", + "OutPredicates", +} + func (wk *worker) embedTraversals(env *otto.Otto, obj *otto.Object) { - obj.Set("In", wk.gremlinFunc("in", obj, env)) - obj.Set("Out", wk.gremlinFunc("out", obj, env)) - obj.Set("Is", wk.gremlinFunc("is", obj, env)) - obj.Set("Both", wk.gremlinFunc("both", obj, env)) - obj.Set("Follow", wk.gremlinFunc("follow", obj, env)) - obj.Set("FollowR", wk.gremlinFollowR("followr", obj, env)) - obj.Set("And", wk.gremlinFunc("and", obj, env)) - obj.Set("Intersect", wk.gremlinFunc("and", obj, env)) - obj.Set("Union", wk.gremlinFunc("or", obj, env)) - obj.Set("Or", wk.gremlinFunc("or", obj, env)) - obj.Set("Back", wk.gremlinBack("back", obj, env)) - obj.Set("Tag", wk.gremlinFunc("tag", obj, env)) - obj.Set("As", wk.gremlinFunc("tag", obj, env)) - obj.Set("Has", wk.gremlinFunc("has", obj, env)) - obj.Set("Save", wk.gremlinFunc("save", obj, env)) - obj.Set("SaveR", wk.gremlinFunc("saver", obj, env)) - obj.Set("Except", wk.gremlinFunc("except", obj, env)) - obj.Set("Difference", wk.gremlinFunc("except", obj, env)) - obj.Set("InPredicates", wk.gremlinFunc("in_predicates", obj, env)) - obj.Set("OutPredicates", wk.gremlinFunc("out_predicates", obj, env)) + for _, t := range traversals { + obj.Set(t, wk.gremlinFunc(t, obj, env)) + } } func (wk *worker) gremlinFunc(kind string, prev *otto.Object, env *otto.Otto) func(otto.FunctionCall) otto.Value { @@ -63,117 +69,6 @@ func (wk *worker) gremlinFunc(kind string, prev *otto.Object, env *otto.Otto) fu } } -func (wk *worker) gremlinBack(kind string, prev *otto.Object, env *otto.Otto) func(otto.FunctionCall) otto.Value { - return func(call otto.FunctionCall) otto.Value { - call.Otto.Run("var out = {}") - out, _ := call.Otto.Object("out") - out.Set("_gremlin_type", kind) - out.Set("_gremlin_values", call.ArgumentList) - args := argsOf(call) - if len(args) > 0 { - out.Set("string_args", args) - } - var otherChain *otto.Object - var thisObj *otto.Object - if len(args) != 0 { - otherChain, thisObj = reverseGremlinChainTo(call.Otto, prev, args[0]) - } else { - otherChain, thisObj = reverseGremlinChainTo(call.Otto, prev, "") - } - out.Set("_gremlin_prev", thisObj) - out.Set("_gremlin_back_chain", otherChain) - wk.embedTraversals(env, out) - if isVertexChain(call.This.Object()) { - wk.embedFinals(env, out) - } - return out.Value() - } -} - -func (wk *worker) gremlinFollowR(kind string, prev *otto.Object, env *otto.Otto) func(otto.FunctionCall) otto.Value { - return func(call otto.FunctionCall) otto.Value { - call.Otto.Run("var out = {}") - out, _ := call.Otto.Object("out") - out.Set("_gremlin_type", kind) - out.Set("_gremlin_values", call.ArgumentList) - args := argsOf(call) - if len(args) > 0 { - out.Set("string_args", args) - } - if len(call.ArgumentList) == 0 { - return prev.Value() - } - arg := call.Argument(0) - if isVertexChain(arg.Object()) { - return prev.Value() - } - newChain, _ := reverseGremlinChainTo(call.Otto, arg.Object(), "") - out.Set("_gremlin_prev", prev) - out.Set("_gremlin_followr", newChain) - wk.embedTraversals(env, out) - if isVertexChain(call.This.Object()) { - wk.embedFinals(env, out) - } - return out.Value() - } -} - -func reverseGremlinChainTo(env *otto.Otto, prev *otto.Object, tag string) (*otto.Object, *otto.Object) { - env.Run("var _base_object = {}") - base, err := env.Object("_base_object") - if err != nil { - glog.Error(err) - return otto.NullValue().Object(), otto.NullValue().Object() - } - if isVertexChain(prev) { - base.Set("_gremlin_type", "vertex") - } else { - base.Set("_gremlin_type", "morphism") - } - return reverseGremlinChainHelper(env, prev, base, tag) -} - -func reverseGremlinChainHelper(env *otto.Otto, chain *otto.Object, newBase *otto.Object, tag string) (*otto.Object, *otto.Object) { - kindVal, _ := chain.Get("_gremlin_type") - kind := kindVal.String() - - if tag != "" { - if kind == "tag" { - tags := propertiesOf(chain, "string_args") - for _, t := range tags { - if t == tag { - return newBase, chain - } - } - } - } - - if kind == "morphism" || kind == "vertex" { - return newBase, chain - } - var newKind string - switch kind { - case "in": - newKind = "out" - case "out": - newKind = "in" - default: - newKind = kind - } - prev, _ := chain.Get("_gremlin_prev") - env.Run("var out = {}") - out, _ := env.Object("out") - out.Set("_gremlin_type", newKind) - values, _ := chain.Get("_gremlin_values") - out.Set("_gremlin_values", values) - back, _ := chain.Get("_gremlin_back_chain") - out.Set("_gremlin_back_chain", back) - out.Set("_gremlin_prev", newBase) - strings, _ := chain.Get("string_args") - out.Set("string_args", strings) - return reverseGremlinChainHelper(env, prev.Object(), out, tag) -} - func debugChain(obj *otto.Object) bool { val, _ := obj.Get("_gremlin_type") glog.V(2).Infoln(val) From 3d286f5245d4d7991f6a1d4919fbe26404dbea95 Mon Sep 17 00:00:00 2001 From: Barak Michener Date: Wed, 28 Oct 2015 23:12:37 -0400 Subject: [PATCH 5/5] document path context, pipe it through Reversal() as well, and update the godoc --- graph/path/morphism_apply_functions.go | 32 ++++++++-------- graph/path/path.go | 67 ++++++++++++++++++++++++---------- 2 files changed, 64 insertions(+), 35 deletions(-) diff --git a/graph/path/morphism_apply_functions.go b/graph/path/morphism_apply_functions.go index 1339762..7444c20 100644 --- a/graph/path/morphism_apply_functions.go +++ b/graph/path/morphism_apply_functions.go @@ -38,7 +38,7 @@ func join(qs graph.QuadStore, itL, itR graph.Iterator) graph.Iterator { func isMorphism(nodes ...string) morphism { return morphism{ Name: "is", - Reversal: func() morphism { return isMorphism(nodes...) }, + Reversal: func(ctx *context) (morphism, *context) { return isMorphism(nodes...), ctx }, Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { if len(nodes) == 0 { // Acting as a passthrough here is equivalent to @@ -64,7 +64,7 @@ func isMorphism(nodes ...string) morphism { func hasMorphism(via interface{}, nodes ...string) morphism { return morphism{ Name: "has", - Reversal: func() morphism { return hasMorphism(via, nodes...) }, + Reversal: func(ctx *context) (morphism, *context) { return hasMorphism(via, nodes...), ctx }, Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { viaIter := buildViaPath(qs, via). BuildIterator() @@ -104,7 +104,7 @@ func hasMorphism(via interface{}, nodes ...string) morphism { func tagMorphism(tags ...string) morphism { return morphism{ Name: "tag", - Reversal: func() morphism { return tagMorphism(tags...) }, + Reversal: func(ctx *context) (morphism, *context) { return tagMorphism(tags...), ctx }, Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { for _, t := range tags { in.Tagger().Add(t) @@ -119,7 +119,7 @@ func tagMorphism(tags ...string) morphism { func outMorphism(tags []string, via ...interface{}) morphism { return morphism{ Name: "out", - Reversal: func() morphism { return inMorphism(tags, via...) }, + Reversal: func(ctx *context) (morphism, *context) { return inMorphism(tags, via...), ctx }, Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { path := buildViaPath(qs, via...) return inOutIterator(path, in, false, tags), ctx @@ -131,7 +131,7 @@ func outMorphism(tags []string, via ...interface{}) morphism { func inMorphism(tags []string, via ...interface{}) morphism { return morphism{ Name: "in", - Reversal: func() morphism { return outMorphism(tags, via...) }, + Reversal: func(ctx *context) (morphism, *context) { return outMorphism(tags, via...), ctx }, Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { path := buildViaPath(qs, via...) return inOutIterator(path, in, true, tags), ctx @@ -142,7 +142,7 @@ func inMorphism(tags []string, via ...interface{}) morphism { func bothMorphism(tags []string, via ...interface{}) morphism { return morphism{ Name: "in", - Reversal: func() morphism { return bothMorphism(tags, via...) }, + Reversal: func(ctx *context) (morphism, *context) { return bothMorphism(tags, via...), ctx }, Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { path := buildViaPath(qs, via...) inSide := inOutIterator(path, in, true, tags) @@ -159,8 +159,10 @@ func bothMorphism(tags []string, via ...interface{}) morphism { // the given set of nodes in the path. func predicatesMorphism(isIn bool) morphism { m := morphism{ - Name: "out_predicates", - Reversal: func() morphism { panic("not implemented: need a function from predicates to their associated edges") }, + Name: "out_predicates", + Reversal: func(ctx *context) (morphism, *context) { + panic("not implemented: need a function from predicates to their associated edges") + }, Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { dir := quad.Subject if isIn { @@ -181,7 +183,7 @@ func predicatesMorphism(isIn bool) morphism { func iteratorMorphism(it graph.Iterator) morphism { return morphism{ Name: "iterator", - Reversal: func() morphism { return iteratorMorphism(it) }, + Reversal: func(ctx *context) (morphism, *context) { return iteratorMorphism(it), ctx }, Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { return join(qs, it, in), ctx }, @@ -192,7 +194,7 @@ func iteratorMorphism(it graph.Iterator) morphism { func andMorphism(p *Path) morphism { return morphism{ Name: "and", - Reversal: func() morphism { return andMorphism(p) }, + Reversal: func(ctx *context) (morphism, *context) { return andMorphism(p), ctx }, Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { itR := p.BuildIteratorOn(qs) @@ -205,7 +207,7 @@ func andMorphism(p *Path) morphism { func orMorphism(p *Path) morphism { return morphism{ Name: "or", - Reversal: func() morphism { return orMorphism(p) }, + Reversal: func(ctx *context) (morphism, *context) { return orMorphism(p), ctx }, Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { itR := p.BuildIteratorOn(qs) @@ -220,7 +222,7 @@ func orMorphism(p *Path) morphism { func followMorphism(p *Path) morphism { return morphism{ Name: "follow", - Reversal: func() morphism { return followMorphism(p.Reverse()) }, + Reversal: func(ctx *context) (morphism, *context) { return followMorphism(p.Reverse()), ctx }, Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { return p.Morphism()(qs, in), ctx }, @@ -231,7 +233,7 @@ func followMorphism(p *Path) morphism { func exceptMorphism(p *Path) morphism { return morphism{ Name: "except", - Reversal: func() morphism { return exceptMorphism(p) }, + Reversal: func(ctx *context) (morphism, *context) { return exceptMorphism(p), ctx }, Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { subIt := p.BuildIteratorOn(qs) allNodes := qs.NodesAllIterator() @@ -245,7 +247,7 @@ func exceptMorphism(p *Path) morphism { func saveMorphism(via interface{}, tag string) morphism { return morphism{ Name: "save", - Reversal: func() morphism { return saveMorphism(via, tag) }, + Reversal: func(ctx *context) (morphism, *context) { return saveMorphism(via, tag), ctx }, Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { return buildSave(qs, via, tag, in, false), ctx }, @@ -256,7 +258,7 @@ func saveMorphism(via interface{}, tag string) morphism { func saveReverseMorphism(via interface{}, tag string) morphism { return morphism{ Name: "saver", - Reversal: func() morphism { return saveReverseMorphism(via, tag) }, + Reversal: func(ctx *context) (morphism, *context) { return saveReverseMorphism(via, tag), ctx }, Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) { return buildSave(qs, via, tag, in, true), ctx }, diff --git a/graph/path/path.go b/graph/path/path.go index 61f1d45..7e364ef 100644 --- a/graph/path/path.go +++ b/graph/path/path.go @@ -20,13 +20,35 @@ type applyMorphism func(graph.QuadStore, graph.Iterator, *context) (graph.Iterat type morphism struct { Name string - Reversal func() morphism + Reversal func(*context) (morphism, *context) Apply applyMorphism tags []string + context context } +// context allows a high-level change to the way paths are constructed. Some +// functions may change the context, causing following chained calls to act +// cdifferently. +// +// In a sense, this is a global state which can be changed as the path +// continues. And as with dealing with any global state, care should be taken: +// +// When modifying the context in Apply(), please copy the passed struct, +// modifying the relevant fields if need be (or pass the given context onward). +// +// Under Reversal(), any functions that wish to change the context should +// appropriately change the passed context (that is, the context that came after +// them will now be what the application of the function would have been) and +// then yield a pointer to their own member context as the return value. +// +// For more examples, look at the morphisms which claim the individual fields. type context struct { - labelSet Path + // Represents the path to the limiting set of labels that should be considered under traversal. + // inMorphism, outMorphism, et al should constrain edges by this set. + // A nil in this field represents all labels. + // + // Claimed by the withLabel morphism + labelSet *Path } // Path represents either a morphism (a pre-defined path stored for later use), @@ -34,7 +56,7 @@ type context struct { type Path struct { stack []morphism qs graph.QuadStore // Optionally. A nil qs is equivalent to a morphism. - baseContext *context + baseContext context } // IsMorphism returns whether this Path is a morphism. @@ -74,8 +96,11 @@ func NewPath(qs graph.QuadStore) *Path { // Reverse returns a new Path that is the reverse of the current one. func (p *Path) Reverse() *Path { newPath := NewPath(p.qs) + ctx := &newPath.baseContext for i := len(p.stack) - 1; i >= 0; i-- { - newPath.stack = append(newPath.stack, p.stack[i].Reversal()) + var revMorphism morphism + revMorphism, ctx = p.stack[i].Reversal(ctx) + newPath.stack = append(newPath.stack, revMorphism) } return newPath } @@ -153,10 +178,10 @@ func (p *Path) Both(via ...interface{}) *Path { // predicates from the current nodes. // // For example: -// // Returns a list of predicates valid from "bob" -// // -// // Will return []string{"follows"} if there are any things that "follow" Bob -// StartPath(qs, "bob").InPredicates() +// // Returns a list of predicates valid from "bob" +// // +// // Will return []string{"follows"} if there are any things that "follow" Bob +// StartPath(qs, "bob").InPredicates() func (p *Path) InPredicates() *Path { p.stack = append(p.stack, predicatesMorphism(true)) return p @@ -166,11 +191,11 @@ func (p *Path) InPredicates() *Path { // predicates from the current nodes. // // For example: -// // Returns a list of predicates valid from "bob" -// // -// // Will return []string{"follows", "status"} if there are edges from "bob" -// // labelled "follows", and edges from "bob" that describe his "status". -// StartPath(qs, "bob").OutPredicates() +// // Returns a list of predicates valid from "bob" +// // +// // Will return []string{"follows", "status"} if there are edges from "bob" +// // labelled "follows", and edges from "bob" that describe his "status". +// StartPath(qs, "bob").OutPredicates() func (p *Path) OutPredicates() *Path { p.stack = append(p.stack, predicatesMorphism(false)) return p @@ -220,8 +245,8 @@ func (p *Path) FollowReverse(path *Path) *Path { // tag, and propagate that to the result set. // // For example: -// // Will return []map[string]string{{"social_status: "cool"}} -// StartPath(qs, "B").Save("status", "social_status" +// // Will return []map[string]string{{"social_status: "cool"}} +// StartPath(qs, "B").Save("status", "social_status" func (p *Path) Save(via interface{}, tag string) *Path { p.stack = append(p.stack, saveMorphism(via, tag)) return p @@ -244,12 +269,12 @@ func (p *Path) Has(via interface{}, nodes ...string) *Path { // Back returns to a previously tagged place in the path. Any constraints applied after the Tag will remain in effect, but traversal continues from the tagged point instead, not from the end of the chain. // // For example: -// // Will return "bob" iff "bob" is cool -// StartPath(qs, "bob").Tag("person_tag").Out("status").Is("cool").Back("person_tag") +// // Will return "bob" iff "bob" is cool +// StartPath(qs, "bob").Tag("person_tag").Out("status").Is("cool").Back("person_tag") func (p *Path) Back(tag string) *Path { newPath := NewPath(p.qs) i := len(p.stack) - 1 - + ctx := &newPath.baseContext for { if i < 0 { return p.Reverse() @@ -263,7 +288,9 @@ func (p *Path) Back(tag string) *Path { } } } - newPath.stack = append(newPath.stack, p.stack[i].Reversal()) + var revMorphism morphism + revMorphism, ctx = p.stack[i].Reversal(ctx) + newPath.stack = append(newPath.stack, revMorphism) i-- } } @@ -291,7 +318,7 @@ func (p *Path) BuildIteratorOn(qs graph.QuadStore) graph.Iterator { func (p *Path) Morphism() graph.ApplyMorphism { return func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { i := it.Clone() - ctx := p.baseContext + ctx := &p.baseContext for _, m := range p.stack { i, ctx = m.Apply(qs, i, ctx) }