Merge pull request #330 from barakmich/refactor_gremlin

query/gremlin: Refactor in terms of graph/path
This commit is contained in:
Barak Michener 2015-10-28 23:22:09 -04:00
commit 0b91414c4b
7 changed files with 514 additions and 485 deletions

View file

@ -9,3 +9,5 @@
<emily> <follows> <fred> . <emily> <follows> <fred> .
<fred> <follows> <greg> . <fred> <follows> <greg> .
<greg> <status> "cool_person" . <greg> <status> "cool_person" .
<predicates> <are> <follows> .
<predicates> <are> <status> .

View file

@ -15,6 +15,8 @@
package path package path
import ( import (
"fmt"
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
"github.com/google/cayley/graph/iterator" "github.com/google/cayley/graph/iterator"
"github.com/google/cayley/quad" "github.com/google/cayley/quad"
@ -36,13 +38,13 @@ func join(qs graph.QuadStore, itL, itR graph.Iterator) graph.Iterator {
func isMorphism(nodes ...string) morphism { func isMorphism(nodes ...string) morphism {
return morphism{ return morphism{
Name: "is", 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) graph.Iterator { Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) {
if len(nodes) == 0 { if len(nodes) == 0 {
// Acting as a passthrough here is equivalent to // Acting as a passthrough here is equivalent to
// building a NodesAllIterator to Next() or Contains() // building a NodesAllIterator to Next() or Contains()
// from here as in previous versions. // from here as in previous versions.
return in return in, ctx
} }
isNodes := qs.FixedIterator() isNodes := qs.FixedIterator()
@ -52,7 +54,7 @@ func isMorphism(nodes ...string) morphism {
// Anything with fixedIterators will usually have a much // Anything with fixedIterators will usually have a much
// smaller result set, so join isNodes first here. // smaller result set, so join isNodes first here.
return join(qs, isNodes, in) return join(qs, isNodes, in), ctx
}, },
} }
} }
@ -62,8 +64,8 @@ func isMorphism(nodes ...string) morphism {
func hasMorphism(via interface{}, nodes ...string) morphism { func hasMorphism(via interface{}, nodes ...string) morphism {
return morphism{ return morphism{
Name: "has", 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) graph.Iterator { Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) {
viaIter := buildViaPath(qs, via). viaIter := buildViaPath(qs, via).
BuildIterator() BuildIterator()
ends := func() graph.Iterator { ends := func() graph.Iterator {
@ -88,13 +90,13 @@ func hasMorphism(via interface{}, nodes ...string) morphism {
if len(nodes) == 0 { // Where dest involves an All iterator. if len(nodes) == 0 { // Where dest involves an All iterator.
route := join(qs, trail, dest) route := join(qs, trail, dest)
has := iterator.NewHasA(qs, route, quad.Subject) 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. // This looks backwards. That's OK-- see the note above.
route := join(qs, dest, trail) route := join(qs, dest, trail)
has := iterator.NewHasA(qs, route, quad.Subject) has := iterator.NewHasA(qs, route, quad.Subject)
return join(qs, has, in) return join(qs, has, in), ctx
}, },
} }
} }
@ -102,48 +104,88 @@ func hasMorphism(via interface{}, nodes ...string) morphism {
func tagMorphism(tags ...string) morphism { func tagMorphism(tags ...string) morphism {
return morphism{ return morphism{
Name: "tag", Name: "tag",
Reversal: func() morphism { return tagMorphism(tags...) }, Reversal: func(ctx *context) (morphism, *context) { return tagMorphism(tags...), ctx },
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 { for _, t := range tags {
it.Tagger().Add(t) in.Tagger().Add(t)
} }
return it return in, ctx
}, },
tags: tags, tags: tags,
} }
} }
// outMorphism iterates forward one RDF triple or via an entire path. // 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{ return morphism{
Name: "out", Name: "out",
Reversal: func() morphism { return inMorphism(via...) }, Reversal: func(ctx *context) (morphism, *context) { return inMorphism(tags, via...), ctx },
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...) path := buildViaPath(qs, via...)
return inOutIterator(path, it, false) return inOutIterator(path, in, false, tags), ctx
}, },
} }
} }
// inMorphism iterates backwards one RDF triple or via an entire path. // 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{ return morphism{
Name: "in", Name: "in",
Reversal: func() morphism { return outMorphism(via...) }, Reversal: func(ctx *context) (morphism, *context) { return outMorphism(tags, via...), ctx },
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...) path := buildViaPath(qs, via...)
return inOutIterator(path, it, true) return inOutIterator(path, in, true, tags), ctx
}, },
} }
} }
func bothMorphism(tags []string, via ...interface{}) morphism {
return morphism{
Name: "in",
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)
outSide := inOutIterator(path, in.Clone(), false, tags)
or := iterator.NewOr()
or.AddSubIterator(inSide)
or.AddSubIterator(outSide)
return or, ctx
},
}
}
// 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(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 {
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. // iteratorMorphism simply tacks the input iterator onto the chain.
func iteratorMorphism(it graph.Iterator) morphism { func iteratorMorphism(it graph.Iterator) morphism {
return morphism{ return morphism{
Name: "iterator", Name: "iterator",
Reversal: func() morphism { return iteratorMorphism(it) }, Reversal: func(ctx *context) (morphism, *context) { return iteratorMorphism(it), ctx },
Apply: func(qs graph.QuadStore, subIt graph.Iterator) graph.Iterator { Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) {
return join(qs, it, subIt) return join(qs, it, in), ctx
}, },
} }
} }
@ -152,11 +194,11 @@ func iteratorMorphism(it graph.Iterator) morphism {
func andMorphism(p *Path) morphism { func andMorphism(p *Path) morphism {
return morphism{ return morphism{
Name: "and", Name: "and",
Reversal: func() morphism { return andMorphism(p) }, Reversal: func(ctx *context) (morphism, *context) { return andMorphism(p), ctx },
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) itR := p.BuildIteratorOn(qs)
return join(qs, itL, itR) return join(qs, in, itR), ctx
}, },
} }
} }
@ -165,14 +207,14 @@ func andMorphism(p *Path) morphism {
func orMorphism(p *Path) morphism { func orMorphism(p *Path) morphism {
return morphism{ return morphism{
Name: "or", Name: "or",
Reversal: func() morphism { return orMorphism(p) }, Reversal: func(ctx *context) (morphism, *context) { return orMorphism(p), ctx },
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) itR := p.BuildIteratorOn(qs)
or := iterator.NewOr() or := iterator.NewOr()
or.AddSubIterator(itL) or.AddSubIterator(in)
or.AddSubIterator(itR) or.AddSubIterator(itR)
return or return or, ctx
}, },
} }
} }
@ -180,9 +222,9 @@ func orMorphism(p *Path) morphism {
func followMorphism(p *Path) morphism { func followMorphism(p *Path) morphism {
return morphism{ return morphism{
Name: "follow", 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, base graph.Iterator) graph.Iterator { Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) {
return p.Morphism()(qs, base) return p.Morphism()(qs, in), ctx
}, },
} }
} }
@ -191,13 +233,13 @@ func followMorphism(p *Path) morphism {
func exceptMorphism(p *Path) morphism { func exceptMorphism(p *Path) morphism {
return morphism{ return morphism{
Name: "except", Name: "except",
Reversal: func() morphism { return exceptMorphism(p) }, Reversal: func(ctx *context) (morphism, *context) { return exceptMorphism(p), ctx },
Apply: func(qs graph.QuadStore, base graph.Iterator) graph.Iterator { Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) {
in := p.BuildIteratorOn(qs) subIt := p.BuildIteratorOn(qs)
allNodes := qs.NodesAllIterator() allNodes := qs.NodesAllIterator()
notIn := iterator.NewNot(in, allNodes) notIn := iterator.NewNot(subIt, allNodes)
return join(qs, base, notIn) return join(qs, in, notIn), ctx
}, },
} }
} }
@ -205,9 +247,9 @@ func exceptMorphism(p *Path) morphism {
func saveMorphism(via interface{}, tag string) morphism { func saveMorphism(via interface{}, tag string) morphism {
return morphism{ return morphism{
Name: "save", 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, it graph.Iterator) graph.Iterator { Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) {
return buildSave(qs, via, tag, it, false) return buildSave(qs, via, tag, in, false), ctx
}, },
tags: []string{tag}, tags: []string{tag},
} }
@ -216,9 +258,9 @@ func saveMorphism(via interface{}, tag string) morphism {
func saveReverseMorphism(via interface{}, tag string) morphism { func saveReverseMorphism(via interface{}, tag string) morphism {
return morphism{ return morphism{
Name: "saver", 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, it graph.Iterator) graph.Iterator { Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) {
return buildSave(qs, via, tag, it, true) return buildSave(qs, via, tag, in, true), ctx
}, },
tags: []string{tag}, tags: []string{tag},
} }
@ -248,13 +290,16 @@ func buildSave(
return join(qs, from, save) 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 start, goal := quad.Subject, quad.Object
if inIterator { if inIterator {
start, goal = goal, start start, goal = goal, start
} }
viaIter := viaPath.BuildIterator() viaIter := viaPath.BuildIterator()
for _, tag := range tags {
viaIter.Tagger().Add(tag)
}
source := iterator.NewLinksTo(viaPath.qs, from, start) source := iterator.NewLinksTo(viaPath.qs, from, start)
trail := iterator.NewLinksTo(viaPath.qs, viaIter, quad.Predicate) trail := iterator.NewLinksTo(viaPath.qs, viaIter, quad.Predicate)
@ -270,12 +315,22 @@ func buildViaPath(qs graph.QuadStore, via ...interface{}) *Path {
} else if len(via) == 1 { } else if len(via) == 1 {
v := via[0] v := via[0]
switch p := v.(type) { switch p := v.(type) {
case nil:
return PathFromIterator(qs, qs.NodesAllIterator())
case *Path: case *Path:
if p.qs != qs {
newp := &Path{
qs: qs,
baseContext: p.baseContext,
stack: p.stack[:],
}
return newp
}
return p return p
case string: case string:
return StartPath(qs, p) return StartPath(qs, p)
default: default:
panic("Invalid type passed to buildViaPath.") panic(fmt.Sprint("Invalid type passed to buildViaPath. ", p))
} }
} }
var strings []string var strings []string

View file

@ -16,11 +16,39 @@ package path
import "github.com/google/cayley/graph" import "github.com/google/cayley/graph"
type applyMorphism func(graph.QuadStore, graph.Iterator, *context) (graph.Iterator, *context)
type morphism struct { type morphism struct {
Name string Name string
Reversal func() morphism Reversal func(*context) (morphism, *context)
Apply graph.ApplyMorphism Apply applyMorphism
tags []string 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 {
// 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), // Path represents either a morphism (a pre-defined path stored for later use),
@ -28,6 +56,7 @@ type morphism struct {
type Path struct { type Path struct {
stack []morphism stack []morphism
qs graph.QuadStore // Optionally. A nil qs is equivalent to a morphism. qs graph.QuadStore // Optionally. A nil qs is equivalent to a morphism.
baseContext context
} }
// IsMorphism returns whether this Path is a morphism. // IsMorphism returns whether this Path is a morphism.
@ -67,8 +96,11 @@ func NewPath(qs graph.QuadStore) *Path {
// Reverse returns a new Path that is the reverse of the current one. // Reverse returns a new Path that is the reverse of the current one.
func (p *Path) Reverse() *Path { func (p *Path) Reverse() *Path {
newPath := NewPath(p.qs) newPath := NewPath(p.qs)
ctx := &newPath.baseContext
for i := len(p.stack) - 1; i >= 0; i-- { 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 return newPath
} }
@ -91,13 +123,13 @@ func (p *Path) Tag(tags ...string) *Path {
// current nodes, via the given outbound predicate. // current nodes, via the given outbound predicate.
// //
// For example: // 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" // // Will return []string{"F"} if there is a predicate (edge) from "B"
// // to "B" labelled "follows". // // to "F" labelled "follows".
// StartPath(qs, "A").Out("follows") // StartPath(qs, "A").Out("follows")
func (p *Path) Out(via ...interface{}) *Path { func (p *Path) Out(via ...interface{}) *Path {
p.stack = append(p.stack, outMorphism(via...)) p.stack = append(p.stack, outMorphism(nil, via...))
return p return p
} }
@ -111,7 +143,61 @@ func (p *Path) Out(via ...interface{}) *Path {
// // edges from those nodes to "B" labelled "follows". // // edges from those nodes to "B" labelled "follows".
// StartPath(qs, "B").In("follows") // StartPath(qs, "B").In("follows")
func (p *Path) In(via ...interface{}) *Path { 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
}
// 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 return p
} }
@ -122,7 +208,7 @@ func (p *Path) And(path *Path) *Path {
return p 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. // current Path so far, or the given Path.
func (p *Path) Or(path *Path) *Path { func (p *Path) Or(path *Path) *Path {
p.stack = append(p.stack, orMorphism(path)) p.stack = append(p.stack, orMorphism(path))
@ -180,6 +266,35 @@ func (p *Path) Has(via interface{}, nodes ...string) *Path {
return p 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
ctx := &newPath.baseContext
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)
}
}
}
var revMorphism morphism
revMorphism, ctx = p.stack[i].Reversal(ctx)
newPath.stack = append(newPath.stack, revMorphism)
i--
}
}
// BuildIterator returns an iterator from this given Path. Note that you must // 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 // 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 // the ability to fetch the underlying quads. This function will panic if
@ -203,8 +318,9 @@ func (p *Path) BuildIteratorOn(qs graph.QuadStore) graph.Iterator {
func (p *Path) Morphism() graph.ApplyMorphism { func (p *Path) Morphism() graph.ApplyMorphism {
return func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { return func(qs graph.QuadStore, it graph.Iterator) graph.Iterator {
i := it.Clone() i := it.Clone()
ctx := &p.baseContext
for _, m := range p.stack { for _, m := range p.stack {
i = m.Apply(qs, i) i, ctx = m.Apply(qs, i, ctx)
} }
return i return i
} }

View file

@ -15,12 +15,15 @@
package path package path
import ( import (
"io"
"os"
"reflect" "reflect"
"sort" "sort"
"testing" "testing"
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
"github.com/google/cayley/quad" "github.com/google/cayley/quad"
"github.com/google/cayley/quad/cquads"
_ "github.com/google/cayley/graph/memstore" _ "github.com/google/cayley/graph/memstore"
_ "github.com/google/cayley/writer" _ "github.com/google/cayley/writer"
@ -28,38 +31,43 @@ import (
// This is a simple test graph. // This is a simple test graph.
// //
// +---+ +---+ // +-------+ +------+
// | A |------- ->| F |<-- // | alice |----- ->| fred |<--
// +---+ \------>+---+-/ +---+ \--+---+ // +-------+ \---->+-------+-/ +------+ \-+-------+
// ------>|#B#| | | E | // ----->| #bob# | | | emily |
// +---+-------/ >+---+ | +---+ // +---------+--/ --->+-------+ | +-------+
// | C | / v // | charlie | / v
// +---+ -/ +---+ // +---------+ / +--------+
// ---- +---+/ |#G#| // \--- +--------+ | #greg# |
// \-->|#D#|------------->+---+ // \-->| #dani# |------------>+--------+
// +---+ // +--------+
//
var simpleGraph = []quad.Quad{ func loadGraph(path string, t testing.TB) []quad.Quad {
{"A", "follows", "B", ""}, var r io.Reader
{"C", "follows", "B", ""}, var simpleGraph []quad.Quad
{"C", "follows", "D", ""}, f, err := os.Open(path)
{"D", "follows", "B", ""}, if err != nil {
{"B", "follows", "F", ""}, t.Fatalf("Failed to open %q: %v", path, err)
{"F", "follows", "G", ""}, }
{"D", "follows", "G", ""}, defer f.Close()
{"E", "follows", "F", ""}, r = f
{"B", "status", "cool", "status_graph"},
{"D", "status", "cool", "status_graph"}, dec := cquads.NewDecoder(r)
{"G", "status", "cool", "status_graph"}, q1, err := dec.Unmarshal()
{"predicates", "are", "follows", ""}, if err != nil {
{"predicates", "are", "status", ""}, 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) qs, _ := graph.NewQuadStore("memstore", "", nil)
w, _ := graph.NewQuadWriter("single", qs, nil) w, _ := graph.NewQuadWriter("single", qs, nil)
for _, t := range data { for _, t := range simpleGraph {
w.AddQuad(t) w.AddQuad(t)
} }
return qs return qs
@ -100,93 +108,131 @@ type test struct {
tag string tag string
} }
// Define morphisms without a QuadStore
var (
grandfollows = StartMorphism().Out("follows").Out("follows")
)
func testSet(qs graph.QuadStore) []test { func testSet(qs graph.QuadStore) []test {
return []test{ return []test{
{ {
message: "use out", message: "use out",
path: StartPath(qs, "A").Out("follows"), path: StartPath(qs, "alice").Out("follows"),
expect: []string{"B"}, expect: []string{"bob"},
}, },
{ {
message: "use in", message: "use in",
path: StartPath(qs, "B").In("follows"), path: StartPath(qs, "bob").In("follows"),
expect: []string{"A", "C", "D"}, expect: []string{"alice", "charlie", "dani"},
}, },
{ {
message: "use path Out", message: "use path Out",
path: StartPath(qs, "B").Out(StartPath(qs, "predicates").Out("are")), path: StartPath(qs, "bob").Out(StartPath(qs, "predicates").Out("are")),
expect: []string{"F", "cool"}, expect: []string{"fred", "cool_person"},
}, },
{ {
message: "use And", message: "use And",
path: StartPath(qs, "D").Out("follows").And( path: StartPath(qs, "dani").Out("follows").And(
StartPath(qs, "C").Out("follows")), StartPath(qs, "charlie").Out("follows")),
expect: []string{"B"}, expect: []string{"bob"},
}, },
{ {
message: "use Or", message: "use Or",
path: StartPath(qs, "F").Out("follows").Or( path: StartPath(qs, "fred").Out("follows").Or(
StartPath(qs, "A").Out("follows")), StartPath(qs, "alice").Out("follows")),
expect: []string{"B", "G"}, expect: []string{"bob", "greg"},
}, },
{ {
message: "implicit All", message: "implicit All",
path: StartPath(qs), 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", message: "follow",
path: StartPath(qs, "C").Follow(StartMorphism().Out("follows").Out("follows")), path: StartPath(qs, "charlie").Follow(StartMorphism().Out("follows").Out("follows")),
expect: []string{"B", "F", "G"}, expect: []string{"bob", "fred", "greg"},
}, },
{ {
message: "followR", message: "followR",
path: StartPath(qs, "F").FollowReverse(StartMorphism().Out("follows").Out("follows")), path: StartPath(qs, "fred").FollowReverse(StartMorphism().Out("follows").Out("follows")),
expect: []string{"A", "C", "D"}, expect: []string{"alice", "charlie", "dani"},
}, },
{ {
message: "is, tag, instead of FollowR", message: "is, tag, instead of FollowR",
path: StartPath(qs).Tag("first").Follow(StartMorphism().Out("follows").Out("follows")).Is("F"), path: StartPath(qs).Tag("first").Follow(StartMorphism().Out("follows").Out("follows")).Is("fred"),
expect: []string{"A", "C", "D"}, expect: []string{"alice", "charlie", "dani"},
tag: "first", tag: "first",
}, },
{ {
message: "use Except to filter out a single vertex", message: "use Except to filter out a single vertex",
path: StartPath(qs, "A", "B").Except(StartPath(qs, "A")), path: StartPath(qs, "alice", "bob").Except(StartPath(qs, "alice")),
expect: []string{"B"}, expect: []string{"bob"},
}, },
{ {
message: "use chained Except", message: "use chained Except",
path: StartPath(qs, "A", "B", "C").Except(StartPath(qs, "B")).Except(StartPath(qs, "A")), path: StartPath(qs, "alice", "bob", "charlie").Except(StartPath(qs, "bob")).Except(StartPath(qs, "alice")),
expect: []string{"C"}, expect: []string{"charlie"},
}, },
{ {
message: "show a simple save", message: "show a simple save",
path: StartPath(qs).Save("status", "somecool"), path: StartPath(qs).Save("status", "somecool"),
tag: "somecool", tag: "somecool",
expect: []string{"cool", "cool", "cool"}, expect: []string{"cool_person", "cool_person", "cool_person"},
}, },
{ {
message: "show a simple saveR", message: "show a simple saveR",
path: StartPath(qs, "cool").SaveReverse("status", "who"), path: StartPath(qs, "cool_person").SaveReverse("status", "who"),
tag: "who", tag: "who",
expect: []string{"G", "D", "B"}, expect: []string{"greg", "dani", "bob"},
}, },
{ {
message: "show a simple Has", message: "show a simple Has",
path: StartPath(qs).Has("status", "cool"), path: StartPath(qs).Has("status", "cool_person"),
expect: []string{"G", "D", "B"}, expect: []string{"greg", "dani", "bob"},
}, },
{ {
message: "show a double Has", message: "show a double Has",
path: StartPath(qs).Has("status", "cool").Has("follows", "F"), path: StartPath(qs).Has("status", "cool_person").Has("follows", "fred"),
expect: []string{"B"}, 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").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"},
}, },
} }
} }
func TestMorphisms(t *testing.T) { func TestMorphisms(t *testing.T) {
qs := makeTestStore(simpleGraph) qs := makeTestStore(t)
for _, test := range testSet(qs) { for _, test := range testSet(qs) {
var got []string var got []string
if test.tag == "" { if test.tag == "" {

View file

@ -15,6 +15,7 @@
package gremlin package gremlin
import ( import (
"fmt"
"strconv" "strconv"
"github.com/barakmich/glog" "github.com/barakmich/glog"
@ -22,7 +23,7 @@ import (
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
"github.com/google/cayley/graph/iterator" "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 { 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) { if !isVertexChain(obj) {
return iterator.NewNull() 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 { func stringsFrom(obj *otto.Object) []string {
@ -57,32 +184,30 @@ func stringsFrom(obj *otto.Object) []string {
return output return output
} }
func buildIteratorFromValue(val otto.Value, qs graph.QuadStore) graph.Iterator { func buildPathFromValue(val otto.Value) (out []interface{}) {
if val.IsNull() || val.IsUndefined() { if val.IsNull() || val.IsUndefined() {
return qs.NodesAllIterator() return nil
} }
if val.IsPrimitive() { if val.IsPrimitive() {
thing, _ := val.Export() thing, _ := val.Export()
switch v := thing.(type) { switch v := thing.(type) {
case string: case string:
it := qs.FixedIterator() out = append(out, v)
it.Add(qs.ValueOf(v)) return
return it
default: default:
glog.Errorln("Trying to build unknown primitive value.") glog.Errorln("Trying to build unknown primitive value.")
} }
} }
switch val.Class() { switch val.Class() {
case "Object": case "Object":
return buildIteratorTree(val.Object(), qs) out = append(out, buildPathFromObject(val.Object()))
return
case "Array": case "Array":
// Had better be an array of strings // Had better be an array of strings
strings := stringsFrom(val.Object()) for _, x := range stringsFrom(val.Object()) {
it := qs.FixedIterator() out = append(out, x)
for _, x := range strings {
it.Add(qs.ValueOf(x))
} }
return it return
case "Number": case "Number":
fallthrough fallthrough
case "Boolean": case "Boolean":
@ -90,248 +215,37 @@ func buildIteratorFromValue(val otto.Value, qs graph.QuadStore) graph.Iterator {
case "Date": case "Date":
fallthrough fallthrough
case "String": case "String":
it := qs.FixedIterator() out = append(out, val.String())
it.Add(qs.ValueOf(val.String())) return
return it
default: default:
glog.Errorln("Trying to handle unsupported Javascript value.") 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") argList, _ := obj.Get("_gremlin_values")
if argList.Class() != "GoArray" { if argList.Class() != "GoArray" {
glog.Errorln("How is arglist not an array? Return nothing.", argList.Class()) glog.Errorln("How is arglist not an array? Return nothing.", argList.Class())
return iterator.NewNull() return nil, nil, false
} }
argArray := argList.Object() argArray := argList.Object()
lengthVal, _ := argArray.Get("length") lengthVal, _ := argArray.Get("length")
length, _ := lengthVal.ToInteger() length, _ := lengthVal.ToInteger()
var predicateNodeIterator graph.Iterator
if length == 0 { if length == 0 {
predicateNodeIterator = qs.NodesAllIterator() predicates = []interface{}{}
} else { } else {
zero, _ := argArray.Get("0") zero, _ := argArray.Get("0")
predicateNodeIterator = buildIteratorFromValue(zero, qs) predicates = buildPathFromValue(zero)
} }
if length >= 2 { if length >= 2 {
var tags []string
one, _ := argArray.Get("1") one, _ := argArray.Get("1")
if one.IsString() { if one.IsString() {
tags = append(tags, one.String()) tags = append(tags, one.String())
} else if one.Class() == "Array" { } else if one.Class() == "Array" {
tags = stringsFrom(one.Object()) tags = stringsFrom(one.Object())
} }
for _, tag := range tags {
predicateNodeIterator.Tagger().Add(tag)
} }
} ok = true
return
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
} }

View file

@ -262,14 +262,14 @@ var testQueries = []struct {
query: ` query: `
g.V().InPredicates().All() g.V().InPredicates().All()
`, `,
expect: []string{"follows", "status"}, expect: []string{"are", "follows", "status"},
}, },
{ {
message: "list all out predicates", message: "list all out predicates",
query: ` query: `
g.V().OutPredicates().All() g.V().OutPredicates().All()
`, `,
expect: []string{"follows", "status"}, expect: []string{"are", "follows", "status"},
}, },
} }
@ -322,6 +322,7 @@ func TestGremlin(t *testing.T) {
got := runQueryGetTag(simpleGraph, test.query, test.tag) got := runQueryGetTag(simpleGraph, test.query, test.tag)
sort.Strings(got) sort.Strings(got)
sort.Strings(test.expect) sort.Strings(test.expect)
t.Log("testing", test.message)
if !reflect.DeepEqual(got, test.expect) { if !reflect.DeepEqual(got, test.expect) {
t.Errorf("Failed to %s, got: %v expected: %v", test.message, got, test.expect) t.Errorf("Failed to %s, got: %v expected: %v", test.message, got, test.expect)
} }

View file

@ -21,27 +21,33 @@ import (
"github.com/robertkrimen/otto" "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) { func (wk *worker) embedTraversals(env *otto.Otto, obj *otto.Object) {
obj.Set("In", wk.gremlinFunc("in", obj, env)) for _, t := range traversals {
obj.Set("Out", wk.gremlinFunc("out", obj, env)) obj.Set(t, wk.gremlinFunc(t, 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))
} }
func (wk *worker) gremlinFunc(kind string, prev *otto.Object, env *otto.Otto) func(otto.FunctionCall) otto.Value { 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 { func debugChain(obj *otto.Object) bool {
val, _ := obj.Get("_gremlin_type") val, _ := obj.Get("_gremlin_type")
glog.V(2).Infoln(val) glog.V(2).Infoln(val)