From f74051a5204642cfc6d30b5d6af1dd15d64c4e42 Mon Sep 17 00:00:00 2001 From: Barak Michener Date: Wed, 28 Oct 2015 21:26:29 -0400 Subject: [PATCH] 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)