bring the path test in line with the gremlin test, add Back()
This commit is contained in:
parent
bcbdb1f82a
commit
b91b7ab824
4 changed files with 130 additions and 63 deletions
|
|
@ -8,4 +8,6 @@
|
||||||
<dani> <status> "cool_person" .
|
<dani> <status> "cool_person" .
|
||||||
<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> .
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,11 @@
|
||||||
|
|
||||||
package path
|
package path
|
||||||
|
|
||||||
import "github.com/google/cayley/graph"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/google/cayley/graph"
|
||||||
|
)
|
||||||
|
|
||||||
type applyMorphism func(graph.QuadStore, graph.Iterator, *context) (graph.Iterator, *context)
|
type applyMorphism func(graph.QuadStore, graph.Iterator, *context) (graph.Iterator, *context)
|
||||||
|
|
||||||
|
|
@ -94,6 +98,11 @@ func (p *Path) Tag(tags ...string) *Path {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// As is a synonym for Tag.
|
||||||
|
func (p *Path) As(tags ...string) *Path {
|
||||||
|
return p.Tag(tags...)
|
||||||
|
}
|
||||||
|
|
||||||
// Out updates this Path to represent the nodes that are adjacent to the
|
// Out updates this Path to represent the nodes that are adjacent to the
|
||||||
// current nodes, via the given outbound predicate.
|
// current nodes, via the given outbound predicate.
|
||||||
//
|
//
|
||||||
|
|
@ -129,7 +138,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))
|
||||||
|
|
@ -187,6 +196,34 @@ 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
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newPath.stack = append(newPath.stack, p.stack[i].Reversal())
|
||||||
|
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
|
||||||
|
|
@ -217,3 +254,12 @@ func (p *Path) Morphism() graph.ApplyMorphism {
|
||||||
return i
|
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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -104,89 +112,100 @@ 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").As("f").Out("follows").Out("status").Is("cool_person").Back("f").In("follows").In("follows").As("acd").Out("status").Is("cool_person").Back("f"),
|
||||||
|
tag: "acd",
|
||||||
|
expect: []string{"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 == "" {
|
||||||
|
|
|
||||||
|
|
@ -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"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue