Merge pull request #333 from barakmich/label_queries

query/gremlin, graph/path: Add support for querying with labels
This commit is contained in:
Barak Michener 2016-01-02 16:55:34 -08:00
commit 792fe5d42c
8 changed files with 145 additions and 21 deletions

View file

@ -16,6 +16,7 @@ package path
import (
"fmt"
"reflect"
"github.com/google/cayley/graph"
"github.com/google/cayley/graph/iterator"
@ -25,11 +26,14 @@ import (
// join puts two iterators together by intersecting their result sets with an AND
// Since we're using an and iterator, it's a good idea to put the smallest result
// set first so that Next() produces fewer values to check Contains().
func join(qs graph.QuadStore, itL, itR graph.Iterator) graph.Iterator {
func join(qs graph.QuadStore, its ...graph.Iterator) graph.Iterator {
and := iterator.NewAnd(qs)
and.AddSubIterator(itL)
and.AddSubIterator(itR)
for _, it := range its {
if it == nil {
continue
}
and.AddSubIterator(it)
}
return and
}
@ -122,8 +126,9 @@ func outMorphism(tags []string, via ...interface{}) morphism {
Reversal: func(ctx *context) (morphism, *context) { return inMorphism(tags, via...), ctx },
Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) {
path := buildViaPath(qs, via...)
return inOutIterator(path, in, false, tags), ctx
return inOutIterator(path, in, false, tags, ctx), ctx
},
tags: tags,
}
}
@ -134,8 +139,9 @@ func inMorphism(tags []string, via ...interface{}) morphism {
Reversal: func(ctx *context) (morphism, *context) { return outMorphism(tags, via...), ctx },
Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) {
path := buildViaPath(qs, via...)
return inOutIterator(path, in, true, tags), ctx
return inOutIterator(path, in, true, tags, ctx), ctx
},
tags: tags,
}
}
@ -145,13 +151,38 @@ func bothMorphism(tags []string, via ...interface{}) morphism {
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)
inSide := inOutIterator(path, in, true, tags, ctx)
outSide := inOutIterator(path, in.Clone(), false, tags, ctx)
or := iterator.NewOr()
or.AddSubIterator(inSide)
or.AddSubIterator(outSide)
return or, ctx
},
tags: tags,
}
}
func labelContextMorphism(tags []string, via ...interface{}) morphism {
var path *Path
if len(via) == 0 {
path = nil
} else {
path = buildViaPath(nil, via...)
path = path.Tag(tags...)
}
return morphism{
Name: "label_context",
Reversal: func(ctx *context) (morphism, *context) {
out := ctx.copy()
ctx.labelSet = path
return labelContextMorphism(tags, via...), &out
},
Apply: func(qs graph.QuadStore, in graph.Iterator, ctx *context) (graph.Iterator, *context) {
out := ctx.copy()
out.labelSet = path
return in, &out
},
tags: tags,
}
}
@ -290,7 +321,7 @@ func buildSave(
return join(qs, from, save)
}
func inOutIterator(viaPath *Path, from graph.Iterator, inIterator bool, tags []string) graph.Iterator {
func inOutIterator(viaPath *Path, from graph.Iterator, inIterator bool, tags []string, ctx *context) graph.Iterator {
start, goal := quad.Subject, quad.Object
if inIterator {
start, goal = goal, start
@ -303,8 +334,14 @@ func inOutIterator(viaPath *Path, from graph.Iterator, inIterator bool, tags []s
source := iterator.NewLinksTo(viaPath.qs, from, start)
trail := iterator.NewLinksTo(viaPath.qs, viaIter, quad.Predicate)
route := join(viaPath.qs, source, trail)
var label graph.Iterator
if ctx != nil {
if ctx.labelSet != nil {
labeliter := ctx.labelSet.BuildIteratorOn(viaPath.qs)
label = iterator.NewLinksTo(viaPath.qs, labeliter, quad.Label)
}
}
route := join(viaPath.qs, source, trail, label)
return iterator.NewHasA(viaPath.qs, route, goal)
}
@ -330,7 +367,7 @@ func buildViaPath(qs graph.QuadStore, via ...interface{}) *Path {
case string:
return StartPath(qs, p)
default:
panic(fmt.Sprint("Invalid type passed to buildViaPath. ", p))
panic(fmt.Sprintln("Invalid type passed to buildViaPath.", reflect.TypeOf(v), p))
}
}
var strings []string

View file

@ -23,7 +23,6 @@ type morphism struct {
Reversal func(*context) (morphism, *context)
Apply applyMorphism
tags []string
context context
}
// context allows a high-level change to the way paths are constructed. Some
@ -51,6 +50,12 @@ type context struct {
labelSet *Path
}
func (c context) copy() context {
return context{
labelSet: c.labelSet,
}
}
// Path represents either a morphism (a pre-defined path stored for later use),
// or a concrete path, consisting of a morphism and an underlying QuadStore.
type Path struct {
@ -266,6 +271,20 @@ func (p *Path) Has(via interface{}, nodes ...string) *Path {
return p
}
// LabelContext restricts the following operations (such as In, Out) to only
// traverse edges that match the given set of labels.
func (p *Path) LabelContext(via ...interface{}) *Path {
p.stack = append(p.stack, labelContextMorphism(nil, via...))
return p
}
// LabelContextWithTags is exactly like LabelContext, except it tags the value
// of the label used in the traversal with the tags provided.
func (p *Path) LabelContextWithTags(tags []string, via ...interface{}) *Path {
p.stack = append(p.stack, labelContextMorphism(tags, via...))
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:

View file

@ -146,7 +146,7 @@ func testSet(qs graph.QuadStore) []test {
{
message: "implicit All",
path: StartPath(qs),
expect: []string{"alice", "bob", "charlie", "dani", "emily", "fred", "greg", "follows", "status", "cool_person", "predicates", "are"},
expect: []string{"alice", "bob", "charlie", "dani", "emily", "fred", "greg", "follows", "status", "cool_person", "predicates", "are", "smart_graph", "smart_person"},
},
{
message: "follow",
@ -178,7 +178,7 @@ func testSet(qs graph.QuadStore) []test {
message: "show a simple save",
path: StartPath(qs).Save("status", "somecool"),
tag: "somecool",
expect: []string{"cool_person", "cool_person", "cool_person"},
expect: []string{"cool_person", "cool_person", "cool_person", "smart_person", "smart_person"},
},
{
message: "show a simple saveR",
@ -228,6 +228,22 @@ func testSet(qs graph.QuadStore) []test {
path: StartPath(qs, "fred").FollowReverse(grandfollows),
expect: []string{"alice", "charlie", "dani"},
},
// Context tests
{
message: "query without label limitation",
path: StartPath(qs, "greg").Out("status"),
expect: []string{"smart_person", "cool_person"},
},
{
message: "query with label limitation",
path: StartPath(qs, "greg").LabelContext("smart_graph").Out("status"),
expect: []string{"smart_person"},
},
{
message: "reverse context",
path: StartPath(qs, "greg").Tag("base").LabelContext("smart_graph").Out("status").Tag("status").Back("base"),
expect: []string{"greg"},
},
}
}