diff --git a/graph/iterator.go b/graph/iterator.go index 83f1999..cba3100 100644 --- a/graph/iterator.go +++ b/graph/iterator.go @@ -206,6 +206,7 @@ const ( Not Optional Materialize + Loop ) var ( @@ -227,6 +228,7 @@ var ( "not", "optional", "materialize", + "loop", } ) diff --git a/graph/iterator/entrypoint_iterator.go b/graph/iterator/entrypoint_iterator.go new file mode 100644 index 0000000..d7185f0 --- /dev/null +++ b/graph/iterator/entrypoint_iterator.go @@ -0,0 +1,91 @@ +package iterator + +import ( + "fmt" + + "github.com/google/cayley/graph" +) + +type EntryPoint struct { + uid uint64 + primaryIt graph.Iterator +} + +func NewEntryPoint(it graph.Iterator) *EntryPoint { + return &EntryPoint{ + uid: NextUID(), + primaryIt: it, + } +} + +func (it *EntryPoint) UID() uint64 { + return it.uid +} + +func (it *EntryPoint) Tagger() *graph.Tagger { + return nil +} + +func (it *EntryPoint) TagResults(dst map[string]graph.Value) { + it.primaryIt.TagResults(dst) +} + +func (it *EntryPoint) Contains(val graph.Value) bool { + return it.primaryIt.Contains(val) +} + +func (it *EntryPoint) Clone() graph.Iterator { + return NewEntryPoint(it.primaryIt) +} + +func (it *EntryPoint) Type() graph.Type { return it.primaryIt.Type() } + +func (it *EntryPoint) Reset() { + it.primaryIt.Reset() +} + +func (it *EntryPoint) SetIterator(iterator graph.Iterator) { + it.primaryIt = iterator + it.primaryIt.Reset() +} + +func (it *EntryPoint) Next() bool { + return graph.Next(it.primaryIt) +} + +func (it *EntryPoint) Result() graph.Value { + return it.primaryIt.Result() +} + +func (it *EntryPoint) NextPath() bool { + return it.primaryIt.NextPath() +} + +func (it *EntryPoint) Stats() graph.IteratorStats { + return it.primaryIt.Stats() +} + +func (it *EntryPoint) Size() (int64, bool) { + return it.Stats().Size, false +} + +func (it *EntryPoint) Optimize() (graph.Iterator, bool) { + return it, false +} + +func (it *EntryPoint) SubIterators() []graph.Iterator { + return []graph.Iterator{it.primaryIt} +} + +func (it *EntryPoint) DebugString(indent int) string { + return fmt.Sprintf("todo") +} + +func (it *EntryPoint) Close() { + it.primaryIt.Close() +} + +// DEPRECATED +func (it *EntryPoint) ResultTree() *graph.ResultTree { + return graph.NewResultTree(it.Result()) +} diff --git a/graph/iterator/loop_iterator.go b/graph/iterator/loop_iterator.go new file mode 100644 index 0000000..86e0718 --- /dev/null +++ b/graph/iterator/loop_iterator.go @@ -0,0 +1,218 @@ +package iterator + +import ( + "fmt" + "strings" + + "github.com/google/cayley/graph" +) + +type Loop struct { + uid uint64 + tags graph.Tagger + ts graph.TripleStore + baseIt graph.Iterator + loopIt graph.Iterator + loopEntryIt *EntryPoint + filterIt graph.Iterator + filterEntryIt *EntryPoint + result graph.Value + runstats graph.IteratorStats + prevValuesIt graph.FixedIterator + loops int + bounded bool + loopsCompleted int + finished bool +} + +func NewLoop(ts graph.TripleStore, baseIt, loopIt, filterIt graph.Iterator, loopEntryIt, filterEntryIt *EntryPoint, loops int, bounded bool) *Loop { + return &Loop{ + uid: NextUID(), + ts: ts, + baseIt: baseIt, + loopEntryIt: loopEntryIt, + loopIt: loopIt, + filterEntryIt: filterEntryIt, + filterIt: filterIt, + prevValuesIt: ts.FixedIterator(), + loops: loops, + bounded: bounded, + loopsCompleted: 0, + finished: false, + } +} + +func (it *Loop) UID() uint64 { + return it.uid +} + +func (it *Loop) Tagger() *graph.Tagger { + return &it.tags +} + +func (it *Loop) TagResults(dst map[string]graph.Value) { + for _, tag := range it.tags.Tags() { + dst[tag] = it.Result() + } + + for tag, value := range it.tags.Fixed() { + dst[tag] = value + } + + it.baseIt.TagResults(dst) + it.loopIt.TagResults(dst) +} + +func (it *Loop) Contains(val graph.Value) bool { + graph.ContainsLogIn(it, val) + if it.loopIt.Contains(val) { + return graph.ContainsLogOut(it, val, true) + } + return graph.ContainsLogOut(it, val, false) +} + +func (it *Loop) Clone() graph.Iterator { + out := NewLoop(it.ts, it.baseIt, it.loopIt, it.filterIt, it.loopEntryIt, it.filterEntryIt, it.loops, it.bounded) + out.tags.CopyFrom(it) + return out +} + +func (it *Loop) Type() graph.Type { return graph.Loop } + +func (it *Loop) Reset() { + // Reset the iterators + it.baseIt.Reset() + it.loopEntryIt.SetIterator(it.baseIt) + it.loopIt.Reset() + it.prevValuesIt.Close() + it.prevValuesIt = it.ts.FixedIterator() + + // Reset the state + it.loopsCompleted = 0 + it.finished = false + it.result = nil +} + +func (it *Loop) advanceLoop() { + // Set the loop iterator to feed from the previous iteration results + it.loopEntryIt.SetIterator(it.prevValuesIt) + + // Reset the loop iterator - will also clean the values in the underlying fixed iterator. + it.loopIt.Reset() + + it.filterIt.Reset() + + // Increment the completed loops count + it.loopsCompleted += 1 + + // Mark the loop as finished - no more results can be expected. + // Either the number of loops has been executed, or there are + // no more expandable nodes. + if size, _ := it.prevValuesIt.Size(); (it.bounded && it.loopsCompleted >= it.loops) || size == 0 { + it.finished = true + } + + // Clean the set of values seen in the previous loop + it.prevValuesIt = it.ts.FixedIterator() +} + +func (it *Loop) checkFilter(value graph.Value) bool { + fixed := it.ts.FixedIterator() + fixed.Add(value) + + it.filterEntryIt.SetIterator(fixed) + it.filterIt.Reset() + + //fmt.Println("Before add value") + //it.filterEntryIt.Add(value) + //fmt.Println("After add value") + + //fmt.Println("Before next") + answer := graph.Next(it.filterIt) + //fmt.Println("After next") + return answer +} + +func (it *Loop) Next() bool { + graph.NextLogIn(it) + it.runstats.Next += 1 + + return it.next() +} + +func (it *Loop) next() bool { + // Check if the loop has any more results + if it.finished { + return graph.NextLogOut(it, nil, false) + } + + for i := 0; ; i++ { + if found := graph.Next(it.loopIt); !found { + // A value has not been found, try looping again + it.advanceLoop() + return it.next() + } + + if it.checkFilter(it.loopIt.Result()) { + // A value has been found. + it.result = it.loopIt.Result() + it.prevValuesIt.Add(it.result) + it.runstats.ContainsNext += 1 + + return graph.NextLogOut(it, it.result, true) + } + } +} + +func (it *Loop) Result() graph.Value { + return it.result +} + +func (it *Loop) NextPath() bool { + return it.loopIt.NextPath() +} + +func (it *Loop) Stats() graph.IteratorStats { + subitStats := it.loopIt.Stats() + // TODO(barakmich): These should really come from the triplestore itself + fanoutFactor := int64(20) + checkConstant := int64(1) + nextConstant := int64(2) + return graph.IteratorStats{ + NextCost: nextConstant + subitStats.NextCost, + ContainsCost: checkConstant + subitStats.ContainsCost, + Size: fanoutFactor * subitStats.Size, + Next: it.runstats.Next, + Contains: it.runstats.Contains, + ContainsNext: it.runstats.ContainsNext, + } +} + +func (it *Loop) Size() (int64, bool) { + return it.Stats().Size, false +} + +// TODO +func (it *Loop) Optimize() (graph.Iterator, bool) { + return it, false +} + +func (it *Loop) SubIterators() []graph.Iterator { + return []graph.Iterator{it.baseIt, it.loopIt} +} + +func (it *Loop) DebugString(indent int) string { + return fmt.Sprintf("%s(%s %d \n%s)", + strings.Repeat(" ", indent), + it.Type(), it.UID(), it.baseIt.DebugString(indent+4)) +} + +func (it *Loop) Close() { + it.baseIt.Close() + it.loopIt.Close() +} + +// DEPRECATED +func (it *Loop) ResultTree() *graph.ResultTree { + return graph.NewResultTree(it.Result()) +} diff --git a/graph/iterator/not_iterator.go b/graph/iterator/not_iterator.go new file mode 100644 index 0000000..8b2d0c9 --- /dev/null +++ b/graph/iterator/not_iterator.go @@ -0,0 +1,138 @@ +package iterator + +import "github.com/google/cayley/graph" + +type Not struct { + uid uint64 + tags graph.Tagger + primaryIt graph.Iterator + forbiddenIt graph.Iterator + result graph.Value + runstats graph.IteratorStats +} + +func NewNot(primaryIt, forbiddenIt graph.Iterator) *Not { + return &Not{ + uid: NextUID(), + primaryIt: primaryIt, + forbiddenIt: forbiddenIt, + } +} + +func (it *Not) UID() uint64 { + return it.uid +} + +func (it *Not) Reset() { + it.result = nil + it.primaryIt.Reset() + it.forbiddenIt.Reset() +} + +func (it *Not) Tagger() *graph.Tagger { + return &it.tags +} + +func (it *Not) TagResults(dst map[string]graph.Value) { + for _, tag := range it.tags.Tags() { + dst[tag] = it.Result() + } + + for tag, value := range it.tags.Fixed() { + dst[tag] = value + } + + if it.primaryIt != nil { + it.primaryIt.TagResults(dst) + } + + // todo +} + +func (it *Not) Clone() graph.Iterator { + not := NewNot(it.primaryIt.Clone(), it.forbiddenIt.Clone()) + not.tags.CopyFrom(it) + return not +} + +func (it *Not) SubIterators() []graph.Iterator { + return []graph.Iterator{it.primaryIt, it.forbiddenIt} +} + +func (it *Not) ResultTree() *graph.ResultTree { + tree := graph.NewResultTree(it.Result()) + tree.AddSubtree(it.primaryIt.ResultTree()) + tree.AddSubtree(it.forbiddenIt.ResultTree()) + return tree +} + +func (it *Not) DebugString(indent int) string { + return "todo" +} + +func (it *Not) Next() bool { + graph.NextLogIn(it) + it.runstats.Next += 1 + for graph.Next(it.primaryIt) { + if curr := it.primaryIt.Result(); !it.forbiddenIt.Contains(curr) { + it.result = curr + it.runstats.ContainsNext += 1 + return graph.NextLogOut(it, curr, true) + } + } + return graph.NextLogOut(it, nil, false) +} + +func (it *Not) Result() graph.Value { + return it.result +} + +func (it *Not) Contains(val graph.Value) bool { + graph.ContainsLogIn(it, val) + it.runstats.Contains += 1 + + mainGood := it.primaryIt.Contains(val) + if mainGood { + mainGood = !it.forbiddenIt.Contains(val) + } + return graph.ContainsLogOut(it, val, mainGood) +} + +func (it *Not) NextPath() bool { + if it.primaryIt.NextPath() { + return true + } + return it.forbiddenIt.NextPath() +} + +func (it *Not) Close() { + it.primaryIt.Close() + it.forbiddenIt.Close() +} + +func (it *Not) Type() graph.Type { return graph.Not } + +func (it *Not) Optimize() (graph.Iterator, bool) { + //it.forbiddenIt = NewMaterialize(it.forbiddenIt) + return it, false +} + +func (it *Not) Stats() graph.IteratorStats { + subitStats := it.primaryIt.Stats() + // TODO(barakmich): These should really come from the triplestore itself + fanoutFactor := int64(20) + checkConstant := int64(1) + nextConstant := int64(2) + return graph.IteratorStats{ + NextCost: nextConstant + subitStats.NextCost, + ContainsCost: checkConstant + subitStats.ContainsCost, + Size: fanoutFactor * subitStats.Size, + Next: it.runstats.Next, + Contains: it.runstats.Contains, + ContainsNext: it.runstats.ContainsNext, + } +} + +func (it *Not) Size() (int64, bool) { + return it.Stats().Size, false +} diff --git a/query/gremlin/build_iterator.go b/query/gremlin/build_iterator.go index 02196f4..85ad3a5 100644 --- a/query/gremlin/build_iterator.go +++ b/query/gremlin/build_iterator.go @@ -298,6 +298,79 @@ func buildIteratorTreeHelper(obj *otto.Object, ts graph.TripleStore, base graph. it = buildIteratorTreeHelper(arg.Object(), ts, subIt) case "in": it = buildInOutIterator(obj, ts, subIt, true) + case "not": + // Not is implemented as the difference between the primary iterator + // and the iterator chain of (primaryIt, follow, followR). + // Build the follow iterator + arg, _ := obj.Get("_gremlin_values") + firstArg, _ := arg.Object().Get("0") + if isVertexChain(firstArg.Object()) { + return iterator.NewNull() + } + + // Build the followR iterator + revArg, _ := obj.Get("_gremlin_followr") + if isVertexChain(revArg.Object()) { + return iterator.NewNull() + } + + followIt := buildIteratorTreeHelper(firstArg.Object(), ts, subIt) + forbiddenIt := buildIteratorTreeHelper(revArg.Object(), ts, followIt) + + it = iterator.NewNot(subIt, forbiddenIt) + case "loop": + arg, _ := obj.Get("_gremlin_values") + firstArg, _ := arg.Object().Get("0") + secondArg, _ := arg.Object().Get("1") + thirdArg, _ := arg.Object().Get("2") + + // Parse the loop iterating sequence + // Check if the first argument is a vertex chain + if isVertexChain(firstArg.Object()) { + return iterator.NewNull() + } + + loopEntryIt := iterator.NewEntryPoint(subIt) + loopIt := buildIteratorTreeHelper(firstArg.Object(), ts, loopEntryIt) + + // Parse the number of loops to execute + noLoops := 0 + bounded := false + if secondArg.IsNumber() { + if no, err := secondArg.ToInteger(); err == nil { + noLoops = int(no) + bounded = true + } else { + return iterator.NewNull() + } + } else if secondArg.IsBoolean() { + if boolVal, err := secondArg.ToBoolean(); err == nil && boolVal { + bounded = false + } else { + return iterator.NewNull() + } + } else { + thirdArg = secondArg + } + + // If the number of loops is negative, the loop is unbounded + if noLoops <= 0 { + bounded = false + } else { + bounded = true + } + + filterEntryIt := iterator.NewEntryPoint(nil) + var filterIt graph.Iterator + if thirdArg.IsNull() || thirdArg.IsUndefined() { + filterIt = filterEntryIt + } else if isVertexChain(thirdArg.Object()) { + return iterator.NewNull() + } else { + filterIt = buildIteratorTreeHelper(thirdArg.Object(), ts, filterEntryIt) + } + + it = iterator.NewLoop(ts, subIt, loopIt, filterIt, loopEntryIt, filterEntryIt, noLoops, bounded) } return it } diff --git a/query/gremlin/traversals.go b/query/gremlin/traversals.go index 2a2250b..6f752dc 100644 --- a/query/gremlin/traversals.go +++ b/query/gremlin/traversals.go @@ -38,6 +38,8 @@ func (wk *worker) embedTraversals(env *otto.Otto, obj *otto.Object) { 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("Loop", gremlinFunc("loop", obj, env, ses)) + obj.Set("Not", gremlinFollowR("not", obj, env, ses)) } func (wk *worker) gremlinFunc(kind string, prev *otto.Object, env *otto.Otto) func(otto.FunctionCall) otto.Value {