Merge pull request #153 from mataevs/exceptop
Except/Not Operator for Gremlin.
This commit is contained in:
commit
c2fab568c7
5 changed files with 238 additions and 0 deletions
163
graph/iterator/not_iterator.go
Normal file
163
graph/iterator/not_iterator.go
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
package iterator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/cayley/graph"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Not iterator acts like a complement for the primary iterator.
|
||||||
|
// It will return all the vertices which are not part of the primary iterator.
|
||||||
|
type Not struct {
|
||||||
|
uid uint64
|
||||||
|
tags graph.Tagger
|
||||||
|
primaryIt graph.Iterator
|
||||||
|
allIt graph.Iterator
|
||||||
|
result graph.Value
|
||||||
|
runstats graph.IteratorStats
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNot(primaryIt, allIt graph.Iterator) *Not {
|
||||||
|
return &Not{
|
||||||
|
uid: NextUID(),
|
||||||
|
primaryIt: primaryIt,
|
||||||
|
allIt: allIt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *Not) UID() uint64 {
|
||||||
|
return it.uid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset resets the internal iterators and the iterator itself.
|
||||||
|
func (it *Not) Reset() {
|
||||||
|
it.result = nil
|
||||||
|
it.primaryIt.Reset()
|
||||||
|
it.allIt.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *Not) Clone() graph.Iterator {
|
||||||
|
not := NewNot(it.primaryIt.Clone(), it.allIt.Clone())
|
||||||
|
not.tags.CopyFrom(it)
|
||||||
|
return not
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubIterators returns a slice of the sub iterators.
|
||||||
|
// The first iterator is the primary iterator, for which the complement
|
||||||
|
// is generated.
|
||||||
|
func (it *Not) SubIterators() []graph.Iterator {
|
||||||
|
return []graph.Iterator{it.primaryIt, it.allIt}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED
|
||||||
|
func (it *Not) ResultTree() *graph.ResultTree {
|
||||||
|
tree := graph.NewResultTree(it.Result())
|
||||||
|
tree.AddSubtree(it.primaryIt.ResultTree())
|
||||||
|
tree.AddSubtree(it.allIt.ResultTree())
|
||||||
|
return tree
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next advances the Not iterator. It returns whether there is another valid
|
||||||
|
// new value. It fetches the next value of the all iterator which is not
|
||||||
|
// contained by the primary iterator.
|
||||||
|
func (it *Not) Next() bool {
|
||||||
|
graph.NextLogIn(it)
|
||||||
|
it.runstats.Next += 1
|
||||||
|
|
||||||
|
for graph.Next(it.allIt) {
|
||||||
|
if curr := it.allIt.Result(); !it.primaryIt.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains checks whether the passed value is part of the primary iterator's
|
||||||
|
// complement. For a valid value, it updates the Result returned by the iterator
|
||||||
|
// to the value itself.
|
||||||
|
func (it *Not) Contains(val graph.Value) bool {
|
||||||
|
graph.ContainsLogIn(it, val)
|
||||||
|
it.runstats.Contains += 1
|
||||||
|
|
||||||
|
if it.primaryIt.Contains(val) {
|
||||||
|
return graph.ContainsLogOut(it, val, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
it.result = val
|
||||||
|
return graph.ContainsLogOut(it, val, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextPath checks whether there is another path. Not applicable, hence it will
|
||||||
|
// return false.
|
||||||
|
func (it *Not) NextPath() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *Not) Close() {
|
||||||
|
it.primaryIt.Close()
|
||||||
|
it.allIt.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *Not) Type() graph.Type { return graph.Not }
|
||||||
|
|
||||||
|
func (it *Not) Optimize() (graph.Iterator, bool) {
|
||||||
|
// TODO - consider wrapping the primaryIt with a MaterializeIt
|
||||||
|
optimizedPrimaryIt, optimized := it.primaryIt.Optimize()
|
||||||
|
if optimized {
|
||||||
|
it.primaryIt = optimizedPrimaryIt
|
||||||
|
}
|
||||||
|
return it, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *Not) Stats() graph.IteratorStats {
|
||||||
|
primaryStats := it.primaryIt.Stats()
|
||||||
|
allStats := it.allIt.Stats()
|
||||||
|
return graph.IteratorStats{
|
||||||
|
NextCost: allStats.NextCost + primaryStats.ContainsCost,
|
||||||
|
ContainsCost: primaryStats.ContainsCost,
|
||||||
|
Size: allStats.Size - primaryStats.Size,
|
||||||
|
Next: it.runstats.Next,
|
||||||
|
Contains: it.runstats.Contains,
|
||||||
|
ContainsNext: it.runstats.ContainsNext,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *Not) Size() (int64, bool) {
|
||||||
|
return it.Stats().Size, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *Not) Describe() graph.Description {
|
||||||
|
subIts := []graph.Description{
|
||||||
|
it.primaryIt.Describe(),
|
||||||
|
it.allIt.Describe(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return graph.Description{
|
||||||
|
UID: it.UID(),
|
||||||
|
Type: it.Type(),
|
||||||
|
Tags: it.tags.Tags(),
|
||||||
|
Iterators: subIts,
|
||||||
|
}
|
||||||
|
}
|
||||||
44
graph/iterator/not_iterator_test.go
Normal file
44
graph/iterator/not_iterator_test.go
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
package iterator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNotIteratorBasics(t *testing.T) {
|
||||||
|
allIt := NewFixed(Identity)
|
||||||
|
allIt.Add(1)
|
||||||
|
allIt.Add(2)
|
||||||
|
allIt.Add(3)
|
||||||
|
allIt.Add(4)
|
||||||
|
|
||||||
|
toComplementIt := NewFixed(Identity)
|
||||||
|
toComplementIt.Add(2)
|
||||||
|
toComplementIt.Add(4)
|
||||||
|
|
||||||
|
not := NewNot(toComplementIt, allIt)
|
||||||
|
|
||||||
|
if v, _ := not.Size(); v != 2 {
|
||||||
|
t.Errorf("Unexpected iterator size: got:%d, expected: %d", v, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect := []int{1, 3}
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
if got := iterated(not); !reflect.DeepEqual(got, expect) {
|
||||||
|
t.Errorf("Failed to iterate Not correctly on repeat %d: got:%v expected:%v", i, got, expect)
|
||||||
|
}
|
||||||
|
not.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range []int{1, 3} {
|
||||||
|
if !not.Contains(v) {
|
||||||
|
t.Errorf("Failed to correctly check %d as true", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range []int{2, 4} {
|
||||||
|
if not.Contains(v) {
|
||||||
|
t.Errorf("Failed to correctly check %d as false", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -298,6 +298,21 @@ func buildIteratorTreeHelper(obj *otto.Object, qs graph.QuadStore, base graph.It
|
||||||
it = buildIteratorTreeHelper(arg.Object(), qs, subIt)
|
it = buildIteratorTreeHelper(arg.Object(), qs, subIt)
|
||||||
case "in":
|
case "in":
|
||||||
it = buildInOutIterator(obj, qs, subIt, true)
|
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()
|
||||||
|
and.AddSubIterator(subIt)
|
||||||
|
and.AddSubIterator(notIt)
|
||||||
|
it = and
|
||||||
}
|
}
|
||||||
return it
|
return it
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,20 @@ var testQueries = []struct {
|
||||||
tag: "acd",
|
tag: "acd",
|
||||||
expect: []string{"D"},
|
expect: []string{"D"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
message: "use Except to filter out a single vertex",
|
||||||
|
query: `
|
||||||
|
g.V("A", "B").Except(g.V("A")).All()
|
||||||
|
`,
|
||||||
|
expect: []string{"B"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "use chained Except",
|
||||||
|
query: `
|
||||||
|
g.V("A", "B", "C").Except(g.V("B")).Except(g.V("C")).All()
|
||||||
|
`,
|
||||||
|
expect: []string{"A"},
|
||||||
|
},
|
||||||
|
|
||||||
// Morphism tests.
|
// Morphism tests.
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ func (wk *worker) embedTraversals(env *otto.Otto, obj *otto.Object) {
|
||||||
obj.Set("Has", wk.gremlinFunc("has", obj, env))
|
obj.Set("Has", wk.gremlinFunc("has", obj, env))
|
||||||
obj.Set("Save", wk.gremlinFunc("save", obj, env))
|
obj.Set("Save", wk.gremlinFunc("save", obj, env))
|
||||||
obj.Set("SaveR", wk.gremlinFunc("saver", 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))
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue