From 5451d07ff59bf59a09b4f3e04a7c94141211a5d5 Mon Sep 17 00:00:00 2001 From: Barak Michener Date: Mon, 1 Sep 2014 14:07:10 -0400 Subject: [PATCH 01/13] first run at an api --- graph/api/api.go | 157 ++++++++++++++++++++++++++++++++++++++++++++++++++ graph/api/api_test.go | 89 ++++++++++++++++++++++++++++ graph/iterator.go | 3 + 3 files changed, 249 insertions(+) create mode 100644 graph/api/api.go create mode 100644 graph/api/api_test.go diff --git a/graph/api/api.go b/graph/api/api.go new file mode 100644 index 0000000..95fe85f --- /dev/null +++ b/graph/api/api.go @@ -0,0 +1,157 @@ +// Copyright 2014 The Cayley Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +import ( + "github.com/google/cayley/graph" + "github.com/google/cayley/graph/iterator" + "github.com/google/cayley/quad" +) + +type Morphism struct { + Name string + Reversal func() Morphism + Apply graph.MorphismFunc +} + +type iteratorMorphism interface { + Apply(graph.Iterator) graph.Iterator +} + +type Path struct { + stack []Morphism + it graph.Iterator + qs graph.QuadStore +} + +func V(qs graph.QuadStore, nodes ...string) *Path { + fixed := qs.FixedIterator() + for _, n := range nodes { + fixed.Add(qs.ValueOf(n)) + } + return &Path{ + it: fixed, + qs: qs, + } +} + +func PathFromIterator(qs graph.QuadStore, it graph.Iterator) *Path { + return &Path{ + it: it, + qs: qs, + } +} + +func M(qs graph.QuadStore) *Path { + return &Path{ + it: nil, + qs: qs, + } +} + +func (p *Path) IsConcrete() bool { return p.it != nil } + +func (p *Path) Tag(tags ...string) *Path { p.stack = append(p.stack, TagMorphism(tags...)); return p } +func (p *Path) Out(via ...interface{}) *Path { + p.stack = append(p.stack, OutMorphism(p.qs, via...)) + return p +} +func (p *Path) In(via ...interface{}) *Path { + p.stack = append(p.stack, InMorphism(p.qs, via...)) + return p +} + +func (p *Path) BuildIterator() graph.Iterator { + f := p.MorphismFunc() + return f(p.it) +} + +func (p *Path) MorphismFunc() graph.MorphismFunc { + return func(it graph.Iterator) graph.Iterator { + i := it.Clone() + for _, m := range p.stack { + i = m.Apply(i) + } + return i + } +} + +func TagMorphism(tags ...string) Morphism { + return Morphism{ + "tag", + func() Morphism { return TagMorphism(tags...) }, + func(it graph.Iterator) graph.Iterator { + for _, t := range tags { + it.Tagger().Add(t) + } + return it + }} +} + +func OutMorphism(qs graph.QuadStore, via ...interface{}) Morphism { + path := buildViaPath(qs, via...) + return Morphism{ + "out", + func() Morphism { return InMorphism(qs, via...) }, + inOutIterator(path, false), + } +} + +func InMorphism(qs graph.QuadStore, via ...interface{}) Morphism { + path := buildViaPath(qs, via...) + return Morphism{ + "in", + func() Morphism { return OutMorphism(qs, via...) }, + inOutIterator(path, true), + } +} + +func inOutIterator(viaPath *Path, reverse bool) graph.MorphismFunc { + return func(base graph.Iterator) graph.Iterator { + in, out := quad.Subject, quad.Object + if reverse { + in, out = out, in + } + lto := iterator.NewLinksTo(viaPath.qs, base, in) + and := iterator.NewAnd() + and.AddSubIterator(iterator.NewLinksTo(viaPath.qs, viaPath.BuildIterator(), quad.Predicate)) + and.AddSubIterator(lto) + return iterator.NewHasA(viaPath.qs, and, out) + } +} + +func buildViaPath(qs graph.QuadStore, via ...interface{}) *Path { + if len(via) == 0 { + return PathFromIterator(qs, qs.NodesAllIterator()) + } else if len(via) == 1 { + v := via[0] + if path, ok := v.(*Path); ok { + return path + } else if str, ok := v.(string); ok { + return V(qs, str) + } else { + panic("Invalid type passed to buildViaPath.") + } + } + var strings []string + for _, s := range via { + if str, ok := s.(string); ok { + strings = append(strings, str) + } else { + panic("Non-string type passed to long Via path") + } + } + return V(qs, strings...) +} diff --git a/graph/api/api_test.go b/graph/api/api_test.go new file mode 100644 index 0000000..2b31327 --- /dev/null +++ b/graph/api/api_test.go @@ -0,0 +1,89 @@ +// Copyright 2014 The Cayley Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +import ( + "reflect" + "sort" + "testing" + + "github.com/google/cayley/graph" + "github.com/google/cayley/quad" + + _ "github.com/google/cayley/graph/memstore" + _ "github.com/google/cayley/writer" +) + +var simpleGraph = []quad.Quad{ + {"A", "follows", "B", ""}, + {"C", "follows", "B", ""}, + {"C", "follows", "D", ""}, + {"D", "follows", "B", ""}, + {"B", "follows", "F", ""}, + {"F", "follows", "G", ""}, + {"D", "follows", "G", ""}, + {"E", "follows", "F", ""}, + {"B", "status", "cool", "status_graph"}, + {"D", "status", "cool", "status_graph"}, + {"G", "status", "cool", "status_graph"}, +} + +func makeTestStore(data []quad.Quad) graph.QuadStore { + qs, _ := graph.NewQuadStore("memstore", "", nil) + w, _ := graph.NewQuadWriter("single", qs, nil) + for _, t := range data { + w.AddQuad(t) + } + return qs +} + +func runTopLevel(path *Path) []string { + var out []string + it := path.BuildIterator() + it, _ = it.Optimize() + for graph.Next(it) { + v := path.qs.NameOf(it.Result()) + out = append(out, v) + } + return out +} + +type test struct { + message string + path *Path + expect []string +} + +func testSet(qs graph.QuadStore) []test { + return []test{ + { + message: "out", + path: V(qs, "A").Out("follows"), + expect: []string{"B"}, + }, + } +} + +func TestMorphisms(t *testing.T) { + qs := makeTestStore(simpleGraph) + for _, test := range testSet(qs) { + got := runTopLevel(test.path) + sort.Strings(got) + sort.Strings(test.expect) + if !reflect.DeepEqual(got, test.expect) { + t.Errorf("Failed to %s, got: %v expected: %v", test.message, got, test.expect) + } + } +} diff --git a/graph/iterator.go b/graph/iterator.go index 6585ff5..0fdc5a5 100644 --- a/graph/iterator.go +++ b/graph/iterator.go @@ -165,6 +165,9 @@ type Description struct { Iterators []Description `json:",omitempty"` } +// A curried function that can generates a new iterator based on some prior iterator. +type MorphismFunc func(Iterator) Iterator + type Nexter interface { // Next advances the iterator to the next value, which will then be available through // the Result method. It returns false if no further advancement is possible, or if an From 2bceb9c25f7b9205bdf8dbe58f43f109b7d5a007 Mon Sep 17 00:00:00 2001 From: Barak Michener Date: Mon, 1 Sep 2014 14:19:47 -0400 Subject: [PATCH 02/13] implement reverse v1 --- graph/api/api.go | 14 +++++++++++++- graph/api/api_test.go | 7 ++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/graph/api/api.go b/graph/api/api.go index 95fe85f..4ddc5e3 100644 --- a/graph/api/api.go +++ b/graph/api/api.go @@ -61,9 +61,21 @@ func M(qs graph.QuadStore) *Path { } } +func (p *Path) Reverse() *Path { + newPath := M(p.qs) + for i := len(p.stack) - 1; i >= 0; i-- { + newPath.stack = append(newPath.stack, p.stack[i].Reversal()) + } + return newPath +} + func (p *Path) IsConcrete() bool { return p.it != nil } -func (p *Path) Tag(tags ...string) *Path { p.stack = append(p.stack, TagMorphism(tags...)); return p } +func (p *Path) Tag(tags ...string) *Path { + p.stack = append(p.stack, TagMorphism(tags...)) + return p +} + func (p *Path) Out(via ...interface{}) *Path { p.stack = append(p.stack, OutMorphism(p.qs, via...)) return p diff --git a/graph/api/api_test.go b/graph/api/api_test.go index 2b31327..fa2c407 100644 --- a/graph/api/api_test.go +++ b/graph/api/api_test.go @@ -69,10 +69,15 @@ type test struct { func testSet(qs graph.QuadStore) []test { return []test{ { - message: "out", + message: "use out", path: V(qs, "A").Out("follows"), expect: []string{"B"}, }, + { + message: "use in", + path: V(qs, "B").In("follows"), + expect: []string{"A", "C", "D"}, + }, } } From 37bf954db660ae94dc3f7c9aec1b204f99e47840 Mon Sep 17 00:00:00 2001 From: Barak Michener Date: Mon, 1 Sep 2014 14:23:05 -0400 Subject: [PATCH 03/13] test via --- graph/api/api_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/graph/api/api_test.go b/graph/api/api_test.go index fa2c407..8ec7699 100644 --- a/graph/api/api_test.go +++ b/graph/api/api_test.go @@ -38,6 +38,8 @@ var simpleGraph = []quad.Quad{ {"B", "status", "cool", "status_graph"}, {"D", "status", "cool", "status_graph"}, {"G", "status", "cool", "status_graph"}, + {"predicates", "are", "follows", ""}, + {"predicates", "are", "status", ""}, } func makeTestStore(data []quad.Quad) graph.QuadStore { @@ -78,6 +80,11 @@ func testSet(qs graph.QuadStore) []test { path: V(qs, "B").In("follows"), expect: []string{"A", "C", "D"}, }, + { + message: "use path Out", + path: V(qs, "B").Out(V(qs, "predicates").Out("are")), + expect: []string{"F", "cool"}, + }, } } From 7b8b72dc549e68e3d97d1dc1d21b433997518827 Mon Sep 17 00:00:00 2001 From: Barak Michener Date: Tue, 2 Sep 2014 17:15:07 -0400 Subject: [PATCH 04/13] unify morphisms and paths --- graph/api/api.go | 62 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/graph/api/api.go b/graph/api/api.go index 4ddc5e3..a48e666 100644 --- a/graph/api/api.go +++ b/graph/api/api.go @@ -32,31 +32,29 @@ type iteratorMorphism interface { type Path struct { stack []Morphism - it graph.Iterator qs graph.QuadStore } func V(qs graph.QuadStore, nodes ...string) *Path { - fixed := qs.FixedIterator() - for _, n := range nodes { - fixed.Add(qs.ValueOf(n)) - } return &Path{ - it: fixed, + stack: []Morphism{ + IsMorphism(qs, nodes...), + }, qs: qs, } } func PathFromIterator(qs graph.QuadStore, it graph.Iterator) *Path { return &Path{ - it: it, + stack: []Morphism{ + intersectIteratorMorphism(it), + }, qs: qs, } } func M(qs graph.QuadStore) *Path { return &Path{ - it: nil, qs: qs, } } @@ -69,8 +67,6 @@ func (p *Path) Reverse() *Path { return newPath } -func (p *Path) IsConcrete() bool { return p.it != nil } - func (p *Path) Tag(tags ...string) *Path { p.stack = append(p.stack, TagMorphism(tags...)) return p @@ -87,7 +83,7 @@ func (p *Path) In(via ...interface{}) *Path { func (p *Path) BuildIterator() graph.Iterator { f := p.MorphismFunc() - return f(p.it) + return f(p.qs.NodesAllIterator()) } func (p *Path) MorphismFunc() graph.MorphismFunc { @@ -100,6 +96,23 @@ func (p *Path) MorphismFunc() graph.MorphismFunc { } } +func IsMorphism(qs graph.QuadStore, nodes ...string) Morphism { + return Morphism{ + "is", + func() Morphism { return IsMorphism(qs, nodes...) }, + func(it graph.Iterator) graph.Iterator { + fixed := qs.FixedIterator() + for _, n := range nodes { + fixed.Add(qs.ValueOf(n)) + } + and := iterator.NewAnd() + and.AddSubIterator(fixed) + and.AddSubIterator(it) + return and + }, + } +} + func TagMorphism(tags ...string) Morphism { return Morphism{ "tag", @@ -130,6 +143,33 @@ func InMorphism(qs graph.QuadStore, via ...interface{}) Morphism { } } +func intersectIteratorMorphism(it graph.Iterator) Morphism { + return Morphism{ + "iterator", + func() Morphism { return intersectIteratorMorphism(it) }, + func(subIt graph.Iterator) graph.Iterator { + and := iterator.NewAnd() + and.AddSubIterator(it) + and.AddSubIterator(subIt) + return and + }, + } +} + +func AndMorphism(path *Path) Morphism { + return Morphism{ + "and", + func() Morphism { return AndMorphism(path) }, + func(it graph.Iterator) graph.Iterator { + subIt := path.BuildIterator() + and := iterator.NewAnd() + and.AddSubIterator(it) + and.AddSubIterator(subIt) + return and + }, + } +} + func inOutIterator(viaPath *Path, reverse bool) graph.MorphismFunc { return func(base graph.Iterator) graph.Iterator { in, out := quad.Subject, quad.Object From cb2caad603eaea8ca8637cb73e94b357e68a15db Mon Sep 17 00:00:00 2001 From: Barak Michener Date: Tue, 2 Sep 2014 18:33:28 -0400 Subject: [PATCH 05/13] privatize morphism --- graph/api/api.go | 78 +++++++++++++++++++++++++++++++++------------------ graph/api/api_test.go | 6 ++++ 2 files changed, 57 insertions(+), 27 deletions(-) diff --git a/graph/api/api.go b/graph/api/api.go index a48e666..848ff36 100644 --- a/graph/api/api.go +++ b/graph/api/api.go @@ -20,9 +20,9 @@ import ( "github.com/google/cayley/quad" ) -type Morphism struct { +type morphism struct { Name string - Reversal func() Morphism + Reversal func() morphism Apply graph.MorphismFunc } @@ -31,14 +31,14 @@ type iteratorMorphism interface { } type Path struct { - stack []Morphism + stack []morphism qs graph.QuadStore } func V(qs graph.QuadStore, nodes ...string) *Path { return &Path{ - stack: []Morphism{ - IsMorphism(qs, nodes...), + stack: []morphism{ + isMorphism(qs, nodes...), }, qs: qs, } @@ -46,7 +46,7 @@ func V(qs graph.QuadStore, nodes ...string) *Path { func PathFromIterator(qs graph.QuadStore, it graph.Iterator) *Path { return &Path{ - stack: []Morphism{ + stack: []morphism{ intersectIteratorMorphism(it), }, qs: qs, @@ -68,16 +68,26 @@ func (p *Path) Reverse() *Path { } func (p *Path) Tag(tags ...string) *Path { - p.stack = append(p.stack, TagMorphism(tags...)) + p.stack = append(p.stack, tagMorphism(tags...)) return p } func (p *Path) Out(via ...interface{}) *Path { - p.stack = append(p.stack, OutMorphism(p.qs, via...)) + p.stack = append(p.stack, outMorphism(p.qs, via...)) return p } func (p *Path) In(via ...interface{}) *Path { - p.stack = append(p.stack, InMorphism(p.qs, via...)) + p.stack = append(p.stack, inMorphism(p.qs, via...)) + return p +} + +func (p *Path) And(path *Path) *Path { + p.stack = append(p.stack, andMorphism(path)) + return p +} + +func (p *Path) Or(path *Path) *Path { + p.stack = append(p.stack, orMorphism(path)) return p } @@ -96,10 +106,10 @@ func (p *Path) MorphismFunc() graph.MorphismFunc { } } -func IsMorphism(qs graph.QuadStore, nodes ...string) Morphism { - return Morphism{ +func isMorphism(qs graph.QuadStore, nodes ...string) morphism { + return morphism{ "is", - func() Morphism { return IsMorphism(qs, nodes...) }, + func() morphism { return isMorphism(qs, nodes...) }, func(it graph.Iterator) graph.Iterator { fixed := qs.FixedIterator() for _, n := range nodes { @@ -113,10 +123,10 @@ func IsMorphism(qs graph.QuadStore, nodes ...string) Morphism { } } -func TagMorphism(tags ...string) Morphism { - return Morphism{ +func tagMorphism(tags ...string) morphism { + return morphism{ "tag", - func() Morphism { return TagMorphism(tags...) }, + func() morphism { return tagMorphism(tags...) }, func(it graph.Iterator) graph.Iterator { for _, t := range tags { it.Tagger().Add(t) @@ -125,28 +135,28 @@ func TagMorphism(tags ...string) Morphism { }} } -func OutMorphism(qs graph.QuadStore, via ...interface{}) Morphism { +func outMorphism(qs graph.QuadStore, via ...interface{}) morphism { path := buildViaPath(qs, via...) - return Morphism{ + return morphism{ "out", - func() Morphism { return InMorphism(qs, via...) }, + func() morphism { return inMorphism(qs, via...) }, inOutIterator(path, false), } } -func InMorphism(qs graph.QuadStore, via ...interface{}) Morphism { +func inMorphism(qs graph.QuadStore, via ...interface{}) morphism { path := buildViaPath(qs, via...) - return Morphism{ + return morphism{ "in", - func() Morphism { return OutMorphism(qs, via...) }, + func() morphism { return outMorphism(qs, via...) }, inOutIterator(path, true), } } -func intersectIteratorMorphism(it graph.Iterator) Morphism { - return Morphism{ +func intersectIteratorMorphism(it graph.Iterator) morphism { + return morphism{ "iterator", - func() Morphism { return intersectIteratorMorphism(it) }, + func() morphism { return intersectIteratorMorphism(it) }, func(subIt graph.Iterator) graph.Iterator { and := iterator.NewAnd() and.AddSubIterator(it) @@ -156,10 +166,10 @@ func intersectIteratorMorphism(it graph.Iterator) Morphism { } } -func AndMorphism(path *Path) Morphism { - return Morphism{ +func andMorphism(path *Path) morphism { + return morphism{ "and", - func() Morphism { return AndMorphism(path) }, + func() morphism { return andMorphism(path) }, func(it graph.Iterator) graph.Iterator { subIt := path.BuildIterator() and := iterator.NewAnd() @@ -170,6 +180,20 @@ func AndMorphism(path *Path) Morphism { } } +func orMorphism(path *Path) morphism { + return morphism{ + "or", + func() morphism { return orMorphism(path) }, + func(it graph.Iterator) graph.Iterator { + subIt := path.BuildIterator() + and := iterator.NewOr() + and.AddSubIterator(it) + and.AddSubIterator(subIt) + return and + }, + } +} + func inOutIterator(viaPath *Path, reverse bool) graph.MorphismFunc { return func(base graph.Iterator) graph.Iterator { in, out := quad.Subject, quad.Object diff --git a/graph/api/api_test.go b/graph/api/api_test.go index 8ec7699..2b0a4d4 100644 --- a/graph/api/api_test.go +++ b/graph/api/api_test.go @@ -85,6 +85,12 @@ func testSet(qs graph.QuadStore) []test { path: V(qs, "B").Out(V(qs, "predicates").Out("are")), expect: []string{"F", "cool"}, }, + { + message: "in", + path: V(qs, "D").Out("follows").And( + V(qs, "C").Out("follows")), + expect: []string{"B"}, + }, } } From 287ea63e2d18c9c2bc9c0cf334379638c0405de4 Mon Sep 17 00:00:00 2001 From: Barak Michener Date: Thu, 4 Sep 2014 16:30:05 -0400 Subject: [PATCH 06/13] rename to paths --- graph/api/api.go | 14 +++++--------- graph/api/api_test.go | 10 +++++----- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/graph/api/api.go b/graph/api/api.go index 848ff36..928ba61 100644 --- a/graph/api/api.go +++ b/graph/api/api.go @@ -26,16 +26,12 @@ type morphism struct { Apply graph.MorphismFunc } -type iteratorMorphism interface { - Apply(graph.Iterator) graph.Iterator -} - type Path struct { stack []morphism qs graph.QuadStore } -func V(qs graph.QuadStore, nodes ...string) *Path { +func StartPath(qs graph.QuadStore, nodes ...string) *Path { return &Path{ stack: []morphism{ isMorphism(qs, nodes...), @@ -53,14 +49,14 @@ func PathFromIterator(qs graph.QuadStore, it graph.Iterator) *Path { } } -func M(qs graph.QuadStore) *Path { +func NewPath(qs graph.QuadStore) *Path { return &Path{ qs: qs, } } func (p *Path) Reverse() *Path { - newPath := M(p.qs) + newPath := NewPath(p.qs) for i := len(p.stack) - 1; i >= 0; i-- { newPath.stack = append(newPath.stack, p.stack[i].Reversal()) } @@ -216,7 +212,7 @@ func buildViaPath(qs graph.QuadStore, via ...interface{}) *Path { if path, ok := v.(*Path); ok { return path } else if str, ok := v.(string); ok { - return V(qs, str) + return StartPath(qs, str) } else { panic("Invalid type passed to buildViaPath.") } @@ -229,5 +225,5 @@ func buildViaPath(qs graph.QuadStore, via ...interface{}) *Path { panic("Non-string type passed to long Via path") } } - return V(qs, strings...) + return StartPath(qs, strings...) } diff --git a/graph/api/api_test.go b/graph/api/api_test.go index 2b0a4d4..09dbe1f 100644 --- a/graph/api/api_test.go +++ b/graph/api/api_test.go @@ -72,23 +72,23 @@ func testSet(qs graph.QuadStore) []test { return []test{ { message: "use out", - path: V(qs, "A").Out("follows"), + path: StartPath(qs, "A").Out("follows"), expect: []string{"B"}, }, { message: "use in", - path: V(qs, "B").In("follows"), + path: StartPath(qs, "B").In("follows"), expect: []string{"A", "C", "D"}, }, { message: "use path Out", - path: V(qs, "B").Out(V(qs, "predicates").Out("are")), + path: StartPath(qs, "B").Out(StartPath(qs, "predicates").Out("are")), expect: []string{"F", "cool"}, }, { message: "in", - path: V(qs, "D").Out("follows").And( - V(qs, "C").Out("follows")), + path: StartPath(qs, "D").Out("follows").And( + StartPath(qs, "C").Out("follows")), expect: []string{"B"}, }, } From 3dc74329c029983a9620102f057fe6d242fd3702 Mon Sep 17 00:00:00 2001 From: Barak Michener Date: Thu, 4 Sep 2014 16:34:58 -0400 Subject: [PATCH 07/13] rename to path --- graph/api/api.go | 229 ------------------------------------------------ graph/api/api_test.go | 107 ---------------------- graph/path/path.go | 229 ++++++++++++++++++++++++++++++++++++++++++++++++ graph/path/path_test.go | 107 ++++++++++++++++++++++ 4 files changed, 336 insertions(+), 336 deletions(-) delete mode 100644 graph/api/api.go delete mode 100644 graph/api/api_test.go create mode 100644 graph/path/path.go create mode 100644 graph/path/path_test.go diff --git a/graph/api/api.go b/graph/api/api.go deleted file mode 100644 index 928ba61..0000000 --- a/graph/api/api.go +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright 2014 The Cayley Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package api - -import ( - "github.com/google/cayley/graph" - "github.com/google/cayley/graph/iterator" - "github.com/google/cayley/quad" -) - -type morphism struct { - Name string - Reversal func() morphism - Apply graph.MorphismFunc -} - -type Path struct { - stack []morphism - qs graph.QuadStore -} - -func StartPath(qs graph.QuadStore, nodes ...string) *Path { - return &Path{ - stack: []morphism{ - isMorphism(qs, nodes...), - }, - qs: qs, - } -} - -func PathFromIterator(qs graph.QuadStore, it graph.Iterator) *Path { - return &Path{ - stack: []morphism{ - intersectIteratorMorphism(it), - }, - qs: qs, - } -} - -func NewPath(qs graph.QuadStore) *Path { - return &Path{ - qs: qs, - } -} - -func (p *Path) Reverse() *Path { - newPath := NewPath(p.qs) - for i := len(p.stack) - 1; i >= 0; i-- { - newPath.stack = append(newPath.stack, p.stack[i].Reversal()) - } - return newPath -} - -func (p *Path) Tag(tags ...string) *Path { - p.stack = append(p.stack, tagMorphism(tags...)) - return p -} - -func (p *Path) Out(via ...interface{}) *Path { - p.stack = append(p.stack, outMorphism(p.qs, via...)) - return p -} -func (p *Path) In(via ...interface{}) *Path { - p.stack = append(p.stack, inMorphism(p.qs, via...)) - return p -} - -func (p *Path) And(path *Path) *Path { - p.stack = append(p.stack, andMorphism(path)) - return p -} - -func (p *Path) Or(path *Path) *Path { - p.stack = append(p.stack, orMorphism(path)) - return p -} - -func (p *Path) BuildIterator() graph.Iterator { - f := p.MorphismFunc() - return f(p.qs.NodesAllIterator()) -} - -func (p *Path) MorphismFunc() graph.MorphismFunc { - return func(it graph.Iterator) graph.Iterator { - i := it.Clone() - for _, m := range p.stack { - i = m.Apply(i) - } - return i - } -} - -func isMorphism(qs graph.QuadStore, nodes ...string) morphism { - return morphism{ - "is", - func() morphism { return isMorphism(qs, nodes...) }, - func(it graph.Iterator) graph.Iterator { - fixed := qs.FixedIterator() - for _, n := range nodes { - fixed.Add(qs.ValueOf(n)) - } - and := iterator.NewAnd() - and.AddSubIterator(fixed) - and.AddSubIterator(it) - return and - }, - } -} - -func tagMorphism(tags ...string) morphism { - return morphism{ - "tag", - func() morphism { return tagMorphism(tags...) }, - func(it graph.Iterator) graph.Iterator { - for _, t := range tags { - it.Tagger().Add(t) - } - return it - }} -} - -func outMorphism(qs graph.QuadStore, via ...interface{}) morphism { - path := buildViaPath(qs, via...) - return morphism{ - "out", - func() morphism { return inMorphism(qs, via...) }, - inOutIterator(path, false), - } -} - -func inMorphism(qs graph.QuadStore, via ...interface{}) morphism { - path := buildViaPath(qs, via...) - return morphism{ - "in", - func() morphism { return outMorphism(qs, via...) }, - inOutIterator(path, true), - } -} - -func intersectIteratorMorphism(it graph.Iterator) morphism { - return morphism{ - "iterator", - func() morphism { return intersectIteratorMorphism(it) }, - func(subIt graph.Iterator) graph.Iterator { - and := iterator.NewAnd() - and.AddSubIterator(it) - and.AddSubIterator(subIt) - return and - }, - } -} - -func andMorphism(path *Path) morphism { - return morphism{ - "and", - func() morphism { return andMorphism(path) }, - func(it graph.Iterator) graph.Iterator { - subIt := path.BuildIterator() - and := iterator.NewAnd() - and.AddSubIterator(it) - and.AddSubIterator(subIt) - return and - }, - } -} - -func orMorphism(path *Path) morphism { - return morphism{ - "or", - func() morphism { return orMorphism(path) }, - func(it graph.Iterator) graph.Iterator { - subIt := path.BuildIterator() - and := iterator.NewOr() - and.AddSubIterator(it) - and.AddSubIterator(subIt) - return and - }, - } -} - -func inOutIterator(viaPath *Path, reverse bool) graph.MorphismFunc { - return func(base graph.Iterator) graph.Iterator { - in, out := quad.Subject, quad.Object - if reverse { - in, out = out, in - } - lto := iterator.NewLinksTo(viaPath.qs, base, in) - and := iterator.NewAnd() - and.AddSubIterator(iterator.NewLinksTo(viaPath.qs, viaPath.BuildIterator(), quad.Predicate)) - and.AddSubIterator(lto) - return iterator.NewHasA(viaPath.qs, and, out) - } -} - -func buildViaPath(qs graph.QuadStore, via ...interface{}) *Path { - if len(via) == 0 { - return PathFromIterator(qs, qs.NodesAllIterator()) - } else if len(via) == 1 { - v := via[0] - if path, ok := v.(*Path); ok { - return path - } else if str, ok := v.(string); ok { - return StartPath(qs, str) - } else { - panic("Invalid type passed to buildViaPath.") - } - } - var strings []string - for _, s := range via { - if str, ok := s.(string); ok { - strings = append(strings, str) - } else { - panic("Non-string type passed to long Via path") - } - } - return StartPath(qs, strings...) -} diff --git a/graph/api/api_test.go b/graph/api/api_test.go deleted file mode 100644 index 09dbe1f..0000000 --- a/graph/api/api_test.go +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2014 The Cayley Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package api - -import ( - "reflect" - "sort" - "testing" - - "github.com/google/cayley/graph" - "github.com/google/cayley/quad" - - _ "github.com/google/cayley/graph/memstore" - _ "github.com/google/cayley/writer" -) - -var simpleGraph = []quad.Quad{ - {"A", "follows", "B", ""}, - {"C", "follows", "B", ""}, - {"C", "follows", "D", ""}, - {"D", "follows", "B", ""}, - {"B", "follows", "F", ""}, - {"F", "follows", "G", ""}, - {"D", "follows", "G", ""}, - {"E", "follows", "F", ""}, - {"B", "status", "cool", "status_graph"}, - {"D", "status", "cool", "status_graph"}, - {"G", "status", "cool", "status_graph"}, - {"predicates", "are", "follows", ""}, - {"predicates", "are", "status", ""}, -} - -func makeTestStore(data []quad.Quad) graph.QuadStore { - qs, _ := graph.NewQuadStore("memstore", "", nil) - w, _ := graph.NewQuadWriter("single", qs, nil) - for _, t := range data { - w.AddQuad(t) - } - return qs -} - -func runTopLevel(path *Path) []string { - var out []string - it := path.BuildIterator() - it, _ = it.Optimize() - for graph.Next(it) { - v := path.qs.NameOf(it.Result()) - out = append(out, v) - } - return out -} - -type test struct { - message string - path *Path - expect []string -} - -func testSet(qs graph.QuadStore) []test { - return []test{ - { - message: "use out", - path: StartPath(qs, "A").Out("follows"), - expect: []string{"B"}, - }, - { - message: "use in", - path: StartPath(qs, "B").In("follows"), - expect: []string{"A", "C", "D"}, - }, - { - message: "use path Out", - path: StartPath(qs, "B").Out(StartPath(qs, "predicates").Out("are")), - expect: []string{"F", "cool"}, - }, - { - message: "in", - path: StartPath(qs, "D").Out("follows").And( - StartPath(qs, "C").Out("follows")), - expect: []string{"B"}, - }, - } -} - -func TestMorphisms(t *testing.T) { - qs := makeTestStore(simpleGraph) - for _, test := range testSet(qs) { - got := runTopLevel(test.path) - sort.Strings(got) - sort.Strings(test.expect) - if !reflect.DeepEqual(got, test.expect) { - t.Errorf("Failed to %s, got: %v expected: %v", test.message, got, test.expect) - } - } -} diff --git a/graph/path/path.go b/graph/path/path.go new file mode 100644 index 0000000..57b4c6e --- /dev/null +++ b/graph/path/path.go @@ -0,0 +1,229 @@ +// Copyright 2014 The Cayley Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package path + +import ( + "github.com/google/cayley/graph" + "github.com/google/cayley/graph/iterator" + "github.com/google/cayley/quad" +) + +type morphism struct { + Name string + Reversal func() morphism + Apply graph.MorphismFunc +} + +type Path struct { + stack []morphism + qs graph.QuadStore +} + +func StartPath(qs graph.QuadStore, nodes ...string) *Path { + return &Path{ + stack: []morphism{ + isMorphism(qs, nodes...), + }, + qs: qs, + } +} + +func PathFromIterator(qs graph.QuadStore, it graph.Iterator) *Path { + return &Path{ + stack: []morphism{ + intersectIteratorMorphism(it), + }, + qs: qs, + } +} + +func NewPath(qs graph.QuadStore) *Path { + return &Path{ + qs: qs, + } +} + +func (p *Path) Reverse() *Path { + newPath := NewPath(p.qs) + for i := len(p.stack) - 1; i >= 0; i-- { + newPath.stack = append(newPath.stack, p.stack[i].Reversal()) + } + return newPath +} + +func (p *Path) Tag(tags ...string) *Path { + p.stack = append(p.stack, tagMorphism(tags...)) + return p +} + +func (p *Path) Out(via ...interface{}) *Path { + p.stack = append(p.stack, outMorphism(p.qs, via...)) + return p +} +func (p *Path) In(via ...interface{}) *Path { + p.stack = append(p.stack, inMorphism(p.qs, via...)) + return p +} + +func (p *Path) And(path *Path) *Path { + p.stack = append(p.stack, andMorphism(path)) + return p +} + +func (p *Path) Or(path *Path) *Path { + p.stack = append(p.stack, orMorphism(path)) + return p +} + +func (p *Path) BuildIterator() graph.Iterator { + f := p.MorphismFunc() + return f(p.qs.NodesAllIterator()) +} + +func (p *Path) MorphismFunc() graph.MorphismFunc { + return func(it graph.Iterator) graph.Iterator { + i := it.Clone() + for _, m := range p.stack { + i = m.Apply(i) + } + return i + } +} + +func isMorphism(qs graph.QuadStore, nodes ...string) morphism { + return morphism{ + "is", + func() morphism { return isMorphism(qs, nodes...) }, + func(it graph.Iterator) graph.Iterator { + fixed := qs.FixedIterator() + for _, n := range nodes { + fixed.Add(qs.ValueOf(n)) + } + and := iterator.NewAnd() + and.AddSubIterator(fixed) + and.AddSubIterator(it) + return and + }, + } +} + +func tagMorphism(tags ...string) morphism { + return morphism{ + "tag", + func() morphism { return tagMorphism(tags...) }, + func(it graph.Iterator) graph.Iterator { + for _, t := range tags { + it.Tagger().Add(t) + } + return it + }} +} + +func outMorphism(qs graph.QuadStore, via ...interface{}) morphism { + path := buildViaPath(qs, via...) + return morphism{ + "out", + func() morphism { return inMorphism(qs, via...) }, + inOutIterator(path, false), + } +} + +func inMorphism(qs graph.QuadStore, via ...interface{}) morphism { + path := buildViaPath(qs, via...) + return morphism{ + "in", + func() morphism { return outMorphism(qs, via...) }, + inOutIterator(path, true), + } +} + +func intersectIteratorMorphism(it graph.Iterator) morphism { + return morphism{ + "iterator", + func() morphism { return intersectIteratorMorphism(it) }, + func(subIt graph.Iterator) graph.Iterator { + and := iterator.NewAnd() + and.AddSubIterator(it) + and.AddSubIterator(subIt) + return and + }, + } +} + +func andMorphism(path *Path) morphism { + return morphism{ + "and", + func() morphism { return andMorphism(path) }, + func(it graph.Iterator) graph.Iterator { + subIt := path.BuildIterator() + and := iterator.NewAnd() + and.AddSubIterator(it) + and.AddSubIterator(subIt) + return and + }, + } +} + +func orMorphism(path *Path) morphism { + return morphism{ + "or", + func() morphism { return orMorphism(path) }, + func(it graph.Iterator) graph.Iterator { + subIt := path.BuildIterator() + and := iterator.NewOr() + and.AddSubIterator(it) + and.AddSubIterator(subIt) + return and + }, + } +} + +func inOutIterator(viaPath *Path, reverse bool) graph.MorphismFunc { + return func(base graph.Iterator) graph.Iterator { + in, out := quad.Subject, quad.Object + if reverse { + in, out = out, in + } + lto := iterator.NewLinksTo(viaPath.qs, base, in) + and := iterator.NewAnd() + and.AddSubIterator(iterator.NewLinksTo(viaPath.qs, viaPath.BuildIterator(), quad.Predicate)) + and.AddSubIterator(lto) + return iterator.NewHasA(viaPath.qs, and, out) + } +} + +func buildViaPath(qs graph.QuadStore, via ...interface{}) *Path { + if len(via) == 0 { + return PathFromIterator(qs, qs.NodesAllIterator()) + } else if len(via) == 1 { + v := via[0] + if path, ok := v.(*Path); ok { + return path + } else if str, ok := v.(string); ok { + return StartPath(qs, str) + } else { + panic("Invalid type passed to buildViaPath.") + } + } + var strings []string + for _, s := range via { + if str, ok := s.(string); ok { + strings = append(strings, str) + } else { + panic("Non-string type passed to long Via path") + } + } + return StartPath(qs, strings...) +} diff --git a/graph/path/path_test.go b/graph/path/path_test.go new file mode 100644 index 0000000..c67ea15 --- /dev/null +++ b/graph/path/path_test.go @@ -0,0 +1,107 @@ +// Copyright 2014 The Cayley Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package path + +import ( + "reflect" + "sort" + "testing" + + "github.com/google/cayley/graph" + "github.com/google/cayley/quad" + + _ "github.com/google/cayley/graph/memstore" + _ "github.com/google/cayley/writer" +) + +var simpleGraph = []quad.Quad{ + {"A", "follows", "B", ""}, + {"C", "follows", "B", ""}, + {"C", "follows", "D", ""}, + {"D", "follows", "B", ""}, + {"B", "follows", "F", ""}, + {"F", "follows", "G", ""}, + {"D", "follows", "G", ""}, + {"E", "follows", "F", ""}, + {"B", "status", "cool", "status_graph"}, + {"D", "status", "cool", "status_graph"}, + {"G", "status", "cool", "status_graph"}, + {"predicates", "are", "follows", ""}, + {"predicates", "are", "status", ""}, +} + +func makeTestStore(data []quad.Quad) graph.QuadStore { + qs, _ := graph.NewQuadStore("memstore", "", nil) + w, _ := graph.NewQuadWriter("single", qs, nil) + for _, t := range data { + w.AddQuad(t) + } + return qs +} + +func runTopLevel(path *Path) []string { + var out []string + it := path.BuildIterator() + it, _ = it.Optimize() + for graph.Next(it) { + v := path.qs.NameOf(it.Result()) + out = append(out, v) + } + return out +} + +type test struct { + message string + path *Path + expect []string +} + +func testSet(qs graph.QuadStore) []test { + return []test{ + { + message: "use out", + path: StartPath(qs, "A").Out("follows"), + expect: []string{"B"}, + }, + { + message: "use in", + path: StartPath(qs, "B").In("follows"), + expect: []string{"A", "C", "D"}, + }, + { + message: "use path Out", + path: StartPath(qs, "B").Out(StartPath(qs, "predicates").Out("are")), + expect: []string{"F", "cool"}, + }, + { + message: "in", + path: StartPath(qs, "D").Out("follows").And( + StartPath(qs, "C").Out("follows")), + expect: []string{"B"}, + }, + } +} + +func TestMorphisms(t *testing.T) { + qs := makeTestStore(simpleGraph) + for _, test := range testSet(qs) { + got := runTopLevel(test.path) + sort.Strings(got) + sort.Strings(test.expect) + if !reflect.DeepEqual(got, test.expect) { + t.Errorf("Failed to %s, got: %v expected: %v", test.message, got, test.expect) + } + } +} From 412813367501257e8ac6193a6eb05ca77c9fb90b Mon Sep 17 00:00:00 2001 From: Barak Michener Date: Sun, 8 Feb 2015 18:10:44 -0500 Subject: [PATCH 08/13] add Is, Follow, and All to the path API --- graph/path/path.go | 35 ++++++++++++++++++++++--- graph/path/path_test.go | 68 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 98 insertions(+), 5 deletions(-) diff --git a/graph/path/path.go b/graph/path/path.go index 57b4c6e..3d13740 100644 --- a/graph/path/path.go +++ b/graph/path/path.go @@ -32,6 +32,9 @@ type Path struct { } func StartPath(qs graph.QuadStore, nodes ...string) *Path { + if len(nodes) == 0 { + return PathFromIterator(qs, qs.NodesAllIterator()) + } return &Path{ stack: []morphism{ isMorphism(qs, nodes...), @@ -43,7 +46,7 @@ func StartPath(qs graph.QuadStore, nodes ...string) *Path { func PathFromIterator(qs graph.QuadStore, it graph.Iterator) *Path { return &Path{ stack: []morphism{ - intersectIteratorMorphism(it), + iteratorMorphism(it), }, qs: qs, } @@ -63,6 +66,11 @@ func (p *Path) Reverse() *Path { return newPath } +func (p *Path) Is(nodes ...string) *Path { + p.stack = append(p.stack, isMorphism(p.qs, nodes...)) + return p +} + func (p *Path) Tag(tags ...string) *Path { p.stack = append(p.stack, tagMorphism(tags...)) return p @@ -87,6 +95,16 @@ func (p *Path) Or(path *Path) *Path { return p } +func (p *Path) Follow(path *Path) *Path { + p.stack = append(p.stack, followMorphism(path)) + return p +} + +func (p *Path) FollowReverse(path *Path) *Path { + p.stack = append(p.stack, followMorphism(path.Reverse())) + return p +} + func (p *Path) BuildIterator() graph.Iterator { f := p.MorphismFunc() return f(p.qs.NodesAllIterator()) @@ -149,10 +167,10 @@ func inMorphism(qs graph.QuadStore, via ...interface{}) morphism { } } -func intersectIteratorMorphism(it graph.Iterator) morphism { +func iteratorMorphism(it graph.Iterator) morphism { return morphism{ "iterator", - func() morphism { return intersectIteratorMorphism(it) }, + func() morphism { return iteratorMorphism(it) }, func(subIt graph.Iterator) graph.Iterator { and := iterator.NewAnd() and.AddSubIterator(it) @@ -190,6 +208,17 @@ func orMorphism(path *Path) morphism { } } +func followMorphism(path *Path) morphism { + return morphism{ + "follow", + func() morphism { return followMorphism(path.Reverse()) }, + func(base graph.Iterator) graph.Iterator { + p := path.MorphismFunc() + return p(base) + }, + } +} + func inOutIterator(viaPath *Path, reverse bool) graph.MorphismFunc { return func(base graph.Iterator) graph.Iterator { in, out := quad.Subject, quad.Object diff --git a/graph/path/path_test.go b/graph/path/path_test.go index c67ea15..d9d6c0d 100644 --- a/graph/path/path_test.go +++ b/graph/path/path_test.go @@ -26,6 +26,20 @@ import ( _ "github.com/google/cayley/writer" ) +// This is a simple test graph. +// +// +---+ +---+ +// | A |------- ->| F |<-- +// +---+ \------>+---+-/ +---+ \--+---+ +// ------>|#B#| | | E | +// +---+-------/ >+---+ | +---+ +// | C | / v +// +---+ -/ +---+ +// ---- +---+/ |#G#| +// \-->|#D#|------------->+---+ +// +---+ +// + var simpleGraph = []quad.Quad{ {"A", "follows", "B", ""}, {"C", "follows", "B", ""}, @@ -62,10 +76,28 @@ func runTopLevel(path *Path) []string { return out } +func runTag(path *Path, tag string) []string { + var out []string + it := path.BuildIterator() + it, _ = it.Optimize() + for graph.Next(it) { + tags := make(map[string]graph.Value) + it.TagResults(tags) + out = append(out, path.qs.NameOf(tags[tag])) + for it.NextPath() { + tags := make(map[string]graph.Value) + it.TagResults(tags) + out = append(out, path.qs.NameOf(tags[tag])) + } + } + return out +} + type test struct { message string path *Path expect []string + tag string } func testSet(qs graph.QuadStore) []test { @@ -86,18 +118,50 @@ func testSet(qs graph.QuadStore) []test { expect: []string{"F", "cool"}, }, { - message: "in", + message: "use And", path: StartPath(qs, "D").Out("follows").And( StartPath(qs, "C").Out("follows")), expect: []string{"B"}, }, + { + message: "use Or", + path: StartPath(qs, "F").Out("follows").Or( + StartPath(qs, "A").Out("follows")), + expect: []string{"B", "G"}, + }, + { + message: "implicit All", + path: StartPath(qs), + expect: []string{"A", "B", "C", "D", "E", "F", "G", "follows", "status", "cool", "status_graph", "predicates", "are"}, + }, + { + message: "follow", + path: StartPath(qs, "C").Follow(StartPath(qs).Out("follows").Out("follows")), + expect: []string{"B", "F", "G"}, + }, + { + message: "followR", + path: StartPath(qs, "F").FollowReverse(StartPath(qs).Out("follows").Out("follows")), + expect: []string{"A", "C", "D"}, + }, + { + message: "is, tag, instead of FollowR", + path: StartPath(qs).Tag("first").Follow(StartPath(qs).Out("follows").Out("follows")).Is("F"), + expect: []string{"A", "C", "D"}, + tag: "first", + }, } } func TestMorphisms(t *testing.T) { qs := makeTestStore(simpleGraph) for _, test := range testSet(qs) { - got := runTopLevel(test.path) + var got []string + if test.tag == "" { + got = runTopLevel(test.path) + } else { + got = runTag(test.path, test.tag) + } sort.Strings(got) sort.Strings(test.expect) if !reflect.DeepEqual(got, test.expect) { From b9ca485321948e3c9fd2b5b0d16a0d723c84d9d9 Mon Sep 17 00:00:00 2001 From: Barak Michener Date: Sun, 8 Feb 2015 18:49:51 -0500 Subject: [PATCH 09/13] Late bind the QuadStore and add Except and Tag --- graph/iterator.go | 2 +- graph/path/path.go | 134 +++++++++++++++++++++++++++++++----------------- graph/path/path_test.go | 16 ++++-- 3 files changed, 100 insertions(+), 52 deletions(-) diff --git a/graph/iterator.go b/graph/iterator.go index 0fdc5a5..2ca4426 100644 --- a/graph/iterator.go +++ b/graph/iterator.go @@ -166,7 +166,7 @@ type Description struct { } // A curried function that can generates a new iterator based on some prior iterator. -type MorphismFunc func(Iterator) Iterator +type ApplyMorphismFunc func(QuadStore, Iterator) Iterator type Nexter interface { // Next advances the iterator to the next value, which will then be available through diff --git a/graph/path/path.go b/graph/path/path.go index 3d13740..3847300 100644 --- a/graph/path/path.go +++ b/graph/path/path.go @@ -23,21 +23,24 @@ import ( type morphism struct { Name string Reversal func() morphism - Apply graph.MorphismFunc + Apply graph.ApplyMorphismFunc } type Path struct { stack []morphism - qs graph.QuadStore + qs graph.QuadStore // Optionally. A nil qs is equivalent to a morphism. +} + +func (p *Path) IsMorphism() bool { return p.qs == nil } + +func StartMorphism(nodes ...string) *Path { + return StartPath(nil, nodes...) } func StartPath(qs graph.QuadStore, nodes ...string) *Path { - if len(nodes) == 0 { - return PathFromIterator(qs, qs.NodesAllIterator()) - } return &Path{ stack: []morphism{ - isMorphism(qs, nodes...), + isMorphism(nodes...), }, qs: qs, } @@ -67,7 +70,7 @@ func (p *Path) Reverse() *Path { } func (p *Path) Is(nodes ...string) *Path { - p.stack = append(p.stack, isMorphism(p.qs, nodes...)) + p.stack = append(p.stack, isMorphism(nodes...)) return p } @@ -77,11 +80,11 @@ func (p *Path) Tag(tags ...string) *Path { } func (p *Path) Out(via ...interface{}) *Path { - p.stack = append(p.stack, outMorphism(p.qs, via...)) + p.stack = append(p.stack, outMorphism(via...)) return p } func (p *Path) In(via ...interface{}) *Path { - p.stack = append(p.stack, inMorphism(p.qs, via...)) + p.stack = append(p.stack, inMorphism(via...)) return p } @@ -95,6 +98,11 @@ func (p *Path) Or(path *Path) *Path { return p } +func (p *Path) Except(path *Path) *Path { + p.stack = append(p.stack, exceptMorphism(path)) + return p +} + func (p *Path) Follow(path *Path) *Path { p.stack = append(p.stack, followMorphism(path)) return p @@ -106,31 +114,44 @@ func (p *Path) FollowReverse(path *Path) *Path { } func (p *Path) BuildIterator() graph.Iterator { - f := p.MorphismFunc() - return f(p.qs.NodesAllIterator()) + if p.IsMorphism() { + panic("Building an iterator from a morphism. Bind a QuadStore with BuildIteratorOn(qs)") + } + return p.BuildIteratorOn(p.qs) } -func (p *Path) MorphismFunc() graph.MorphismFunc { - return func(it graph.Iterator) graph.Iterator { +func (p *Path) BuildIteratorOn(qs graph.QuadStore) graph.Iterator { + f := p.MorphismFunc() + return f(qs, qs.NodesAllIterator()) +} + +func (p *Path) MorphismFunc() graph.ApplyMorphismFunc { + return func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { i := it.Clone() for _, m := range p.stack { - i = m.Apply(i) + i = m.Apply(qs, i) } return i } } -func isMorphism(qs graph.QuadStore, nodes ...string) morphism { +func isMorphism(nodes ...string) morphism { return morphism{ "is", - func() morphism { return isMorphism(qs, nodes...) }, - func(it graph.Iterator) graph.Iterator { - fixed := qs.FixedIterator() - for _, n := range nodes { - fixed.Add(qs.ValueOf(n)) + func() morphism { return isMorphism(nodes...) }, + func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { + var sub graph.Iterator + if len(nodes) == 0 { + sub = qs.NodesAllIterator() + } else { + fixed := qs.FixedIterator() + for _, n := range nodes { + fixed.Add(qs.ValueOf(n)) + } + sub = fixed } and := iterator.NewAnd() - and.AddSubIterator(fixed) + and.AddSubIterator(sub) and.AddSubIterator(it) return and }, @@ -141,7 +162,7 @@ func tagMorphism(tags ...string) morphism { return morphism{ "tag", func() morphism { return tagMorphism(tags...) }, - func(it graph.Iterator) graph.Iterator { + func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { for _, t := range tags { it.Tagger().Add(t) } @@ -149,21 +170,25 @@ func tagMorphism(tags ...string) morphism { }} } -func outMorphism(qs graph.QuadStore, via ...interface{}) morphism { - path := buildViaPath(qs, via...) +func outMorphism(via ...interface{}) morphism { return morphism{ "out", - func() morphism { return inMorphism(qs, via...) }, - inOutIterator(path, false), + func() morphism { return inMorphism(via...) }, + func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { + path := buildViaPath(qs, via...) + return inOutIterator(path, it, false) + }, } } -func inMorphism(qs graph.QuadStore, via ...interface{}) morphism { - path := buildViaPath(qs, via...) +func inMorphism(via ...interface{}) morphism { return morphism{ "in", - func() morphism { return outMorphism(qs, via...) }, - inOutIterator(path, true), + func() morphism { return outMorphism(via...) }, + func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { + path := buildViaPath(qs, via...) + return inOutIterator(path, it, true) + }, } } @@ -171,7 +196,7 @@ func iteratorMorphism(it graph.Iterator) morphism { return morphism{ "iterator", func() morphism { return iteratorMorphism(it) }, - func(subIt graph.Iterator) graph.Iterator { + func(_ graph.QuadStore, subIt graph.Iterator) graph.Iterator { and := iterator.NewAnd() and.AddSubIterator(it) and.AddSubIterator(subIt) @@ -184,8 +209,8 @@ func andMorphism(path *Path) morphism { return morphism{ "and", func() morphism { return andMorphism(path) }, - func(it graph.Iterator) graph.Iterator { - subIt := path.BuildIterator() + func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { + subIt := path.BuildIteratorOn(qs) and := iterator.NewAnd() and.AddSubIterator(it) and.AddSubIterator(subIt) @@ -198,8 +223,8 @@ func orMorphism(path *Path) morphism { return morphism{ "or", func() morphism { return orMorphism(path) }, - func(it graph.Iterator) graph.Iterator { - subIt := path.BuildIterator() + func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { + subIt := path.BuildIteratorOn(qs) and := iterator.NewOr() and.AddSubIterator(it) and.AddSubIterator(subIt) @@ -212,27 +237,40 @@ func followMorphism(path *Path) morphism { return morphism{ "follow", func() morphism { return followMorphism(path.Reverse()) }, - func(base graph.Iterator) graph.Iterator { + func(qs graph.QuadStore, base graph.Iterator) graph.Iterator { p := path.MorphismFunc() - return p(base) + return p(qs, base) }, } } -func inOutIterator(viaPath *Path, reverse bool) graph.MorphismFunc { - return func(base graph.Iterator) graph.Iterator { - in, out := quad.Subject, quad.Object - if reverse { - in, out = out, in - } - lto := iterator.NewLinksTo(viaPath.qs, base, in) - and := iterator.NewAnd() - and.AddSubIterator(iterator.NewLinksTo(viaPath.qs, viaPath.BuildIterator(), quad.Predicate)) - and.AddSubIterator(lto) - return iterator.NewHasA(viaPath.qs, and, out) +func exceptMorphism(path *Path) morphism { + return morphism{ + "except", + func() morphism { return exceptMorphism(path) }, + func(qs graph.QuadStore, base graph.Iterator) graph.Iterator { + subIt := path.BuildIteratorOn(qs) + notIt := iterator.NewNot(subIt, qs.NodesAllIterator()) + and := iterator.NewAnd() + and.AddSubIterator(base) + and.AddSubIterator(notIt) + return and + }, } } +func inOutIterator(viaPath *Path, it graph.Iterator, reverse bool) graph.Iterator { + in, out := quad.Subject, quad.Object + if reverse { + in, out = out, in + } + lto := iterator.NewLinksTo(viaPath.qs, it, in) + and := iterator.NewAnd() + and.AddSubIterator(iterator.NewLinksTo(viaPath.qs, viaPath.BuildIterator(), quad.Predicate)) + and.AddSubIterator(lto) + return iterator.NewHasA(viaPath.qs, and, out) +} + func buildViaPath(qs graph.QuadStore, via ...interface{}) *Path { if len(via) == 0 { return PathFromIterator(qs, qs.NodesAllIterator()) diff --git a/graph/path/path_test.go b/graph/path/path_test.go index d9d6c0d..87419f9 100644 --- a/graph/path/path_test.go +++ b/graph/path/path_test.go @@ -136,20 +136,30 @@ func testSet(qs graph.QuadStore) []test { }, { message: "follow", - path: StartPath(qs, "C").Follow(StartPath(qs).Out("follows").Out("follows")), + path: StartPath(qs, "C").Follow(StartMorphism().Out("follows").Out("follows")), expect: []string{"B", "F", "G"}, }, { message: "followR", - path: StartPath(qs, "F").FollowReverse(StartPath(qs).Out("follows").Out("follows")), + path: StartPath(qs, "F").FollowReverse(StartMorphism().Out("follows").Out("follows")), expect: []string{"A", "C", "D"}, }, { message: "is, tag, instead of FollowR", - path: StartPath(qs).Tag("first").Follow(StartPath(qs).Out("follows").Out("follows")).Is("F"), + path: StartPath(qs).Tag("first").Follow(StartMorphism().Out("follows").Out("follows")).Is("F"), expect: []string{"A", "C", "D"}, tag: "first", }, + { + message: "use Except to filter out a single vertex", + path: StartPath(qs, "A", "B").Except(StartPath(qs, "A")), + expect: []string{"B"}, + }, + { + message: "use chained Except", + path: StartPath(qs, "A", "B", "C").Except(StartPath(qs, "B")).Except(StartPath(qs, "A")), + expect: []string{"C"}, + }, } } From 0e50027be8fec9a753b7e1e4b32922b2877ab4c4 Mon Sep 17 00:00:00 2001 From: Barak Michener Date: Tue, 10 Feb 2015 19:07:43 -0500 Subject: [PATCH 10/13] var renames, remove Func, etc --- graph/iterator.go | 4 ++-- graph/path/path.go | 43 +++++++++++++++++++++---------------------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/graph/iterator.go b/graph/iterator.go index 2ca4426..5e41810 100644 --- a/graph/iterator.go +++ b/graph/iterator.go @@ -165,8 +165,8 @@ type Description struct { Iterators []Description `json:",omitempty"` } -// A curried function that can generates a new iterator based on some prior iterator. -type ApplyMorphismFunc func(QuadStore, Iterator) Iterator +// ApplyMorphism is a curried function that can generates a new iterator based on some prior iterator. +type ApplyMorphism func(QuadStore, Iterator) Iterator type Nexter interface { // Next advances the iterator to the next value, which will then be available through diff --git a/graph/path/path.go b/graph/path/path.go index 3847300..b25cac4 100644 --- a/graph/path/path.go +++ b/graph/path/path.go @@ -23,7 +23,7 @@ import ( type morphism struct { Name string Reversal func() morphism - Apply graph.ApplyMorphismFunc + Apply graph.ApplyMorphism } type Path struct { @@ -121,11 +121,10 @@ func (p *Path) BuildIterator() graph.Iterator { } func (p *Path) BuildIteratorOn(qs graph.QuadStore) graph.Iterator { - f := p.MorphismFunc() - return f(qs, qs.NodesAllIterator()) + return p.Morphism()(qs, qs.NodesAllIterator()) } -func (p *Path) MorphismFunc() graph.ApplyMorphismFunc { +func (p *Path) Morphism() graph.ApplyMorphism { return func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { i := it.Clone() for _, m := range p.stack { @@ -205,12 +204,12 @@ func iteratorMorphism(it graph.Iterator) morphism { } } -func andMorphism(path *Path) morphism { +func andMorphism(p *Path) morphism { return morphism{ "and", - func() morphism { return andMorphism(path) }, + func() morphism { return andMorphism(p) }, func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { - subIt := path.BuildIteratorOn(qs) + subIt := p.BuildIteratorOn(qs) and := iterator.NewAnd() and.AddSubIterator(it) and.AddSubIterator(subIt) @@ -219,12 +218,12 @@ func andMorphism(path *Path) morphism { } } -func orMorphism(path *Path) morphism { +func orMorphism(p *Path) morphism { return morphism{ "or", - func() morphism { return orMorphism(path) }, + func() morphism { return orMorphism(p) }, func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { - subIt := path.BuildIteratorOn(qs) + subIt := p.BuildIteratorOn(qs) and := iterator.NewOr() and.AddSubIterator(it) and.AddSubIterator(subIt) @@ -233,23 +232,22 @@ func orMorphism(path *Path) morphism { } } -func followMorphism(path *Path) morphism { +func followMorphism(p *Path) morphism { return morphism{ "follow", - func() morphism { return followMorphism(path.Reverse()) }, + func() morphism { return followMorphism(p.Reverse()) }, func(qs graph.QuadStore, base graph.Iterator) graph.Iterator { - p := path.MorphismFunc() - return p(qs, base) + return p.Morphism()(qs, base) }, } } -func exceptMorphism(path *Path) morphism { +func exceptMorphism(p *Path) morphism { return morphism{ "except", - func() morphism { return exceptMorphism(path) }, + func() morphism { return exceptMorphism(p) }, func(qs graph.QuadStore, base graph.Iterator) graph.Iterator { - subIt := path.BuildIteratorOn(qs) + subIt := p.BuildIteratorOn(qs) notIt := iterator.NewNot(subIt, qs.NodesAllIterator()) and := iterator.NewAnd() and.AddSubIterator(base) @@ -276,11 +274,12 @@ func buildViaPath(qs graph.QuadStore, via ...interface{}) *Path { return PathFromIterator(qs, qs.NodesAllIterator()) } else if len(via) == 1 { v := via[0] - if path, ok := v.(*Path); ok { - return path - } else if str, ok := v.(string); ok { - return StartPath(qs, str) - } else { + switch v := v.(type) { + case *Path: + return v + case string: + return StartPath(qs, v) + default: panic("Invalid type passed to buildViaPath.") } } From b8a214bd941babe6a219ba01af57be87beadf254 Mon Sep 17 00:00:00 2001 From: Andrew Dunham Date: Tue, 14 Apr 2015 15:34:41 -0700 Subject: [PATCH 11/13] Initial work at documenting new Go API --- graph/path/path.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/graph/path/path.go b/graph/path/path.go index b25cac4..02df44b 100644 --- a/graph/path/path.go +++ b/graph/path/path.go @@ -26,17 +26,22 @@ type morphism struct { Apply graph.ApplyMorphism } +// 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 { stack []morphism qs graph.QuadStore // Optionally. A nil qs is equivalent to a morphism. } +// IsMorphism returns whether this Path is a morphism. func (p *Path) IsMorphism() bool { return p.qs == nil } +// StartMorphism creates a new Path with no underlying QuadStore. func StartMorphism(nodes ...string) *Path { return StartPath(nil, nodes...) } +// StartPath creates a new Path from a set of nodes and an underlying QuadStore. func StartPath(qs graph.QuadStore, nodes ...string) *Path { return &Path{ stack: []morphism{ @@ -55,12 +60,14 @@ func PathFromIterator(qs graph.QuadStore, it graph.Iterator) *Path { } } +// NewPath creates a new, empty Path. func NewPath(qs graph.QuadStore) *Path { return &Path{ qs: qs, } } +// Reverse returns a new Path that is the reverse of the current one. func (p *Path) Reverse() *Path { newPath := NewPath(p.qs) for i := len(p.stack) - 1; i >= 0; i-- { @@ -79,25 +86,54 @@ func (p *Path) Tag(tags ...string) *Path { return p } +// Out updates this Path to represent the nodes that are adjacent to the +// current nodes, via the given outbound predicate. +// +// For example: +// // Returns the list of nodes that "A" follows. +// // +// // Will return []string{"B"} if there is a predicate (edge) from "A" +// // to "B" labelled "follows". +// StartPath(qs, "A").Out("follows") func (p *Path) Out(via ...interface{}) *Path { p.stack = append(p.stack, outMorphism(via...)) return p } + +// In updates this Path to represent the nodes that are adjacent to the +// current nodes, via the given inbound predicate. +// +// For example: +// // Return the list of nodes that follow "B". +// // +// // Will return []string{"A", "C", "D"} if there are the appropriate +// // 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...)) return p } +// And updates the current Path to represent the nodes that match both the +// current Path so far, and the given Path. func (p *Path) And(path *Path) *Path { p.stack = append(p.stack, andMorphism(path)) return p } +// And updates the current Path to represent the nodes that match either the +// current Path so far, or the given Path. func (p *Path) Or(path *Path) *Path { p.stack = append(p.stack, orMorphism(path)) return p } +// Except updates the current Path to represent the all of the current nodes +// except those in the supplied Path. +// +// For example: +// // Will return []string{"B"} +// StartPath(qs, "A", "B").Except(StartPath(qs, "A")) func (p *Path) Except(path *Path) *Path { p.stack = append(p.stack, exceptMorphism(path)) return p @@ -113,6 +149,10 @@ func (p *Path) FollowReverse(path *Path) *Path { return p } +// 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 +// the ability to fetch the underlying quads. This function will panic if +// called with a morphism (i.e. if p.IsMorphism() is true). func (p *Path) BuildIterator() graph.Iterator { if p.IsMorphism() { panic("Building an iterator from a morphism. Bind a QuadStore with BuildIteratorOn(qs)") @@ -120,10 +160,15 @@ func (p *Path) BuildIterator() graph.Iterator { return p.BuildIteratorOn(p.qs) } +// BuildIteratorOn will return an iterator for this path on the given QuadStore. func (p *Path) BuildIteratorOn(qs graph.QuadStore) graph.Iterator { return p.Morphism()(qs, qs.NodesAllIterator()) } +// Morphism returns the morphism of this path. The returned value is a +// function that, when given a QuadStore and an existing Iterator, will +// return a new Iterator that yields the subset of values from the existing +// iterator matched by the current Path. func (p *Path) Morphism() graph.ApplyMorphism { return func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { i := it.Clone() From fa9945ed923aea9d19bc6fed1c87df8ef866c2c7 Mon Sep 17 00:00:00 2001 From: Barak Michener Date: Thu, 30 Apr 2015 23:22:49 -0400 Subject: [PATCH 12/13] Add imports file to root; expansion welcome --- graph/path/path.go | 12 ++++++------ graph/quadwriter.go | 4 ++-- imports.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 imports.go diff --git a/graph/path/path.go b/graph/path/path.go index 02df44b..547fb2b 100644 --- a/graph/path/path.go +++ b/graph/path/path.go @@ -194,7 +194,7 @@ func isMorphism(nodes ...string) morphism { } sub = fixed } - and := iterator.NewAnd() + and := iterator.NewAnd(qs) and.AddSubIterator(sub) and.AddSubIterator(it) return and @@ -240,8 +240,8 @@ func iteratorMorphism(it graph.Iterator) morphism { return morphism{ "iterator", func() morphism { return iteratorMorphism(it) }, - func(_ graph.QuadStore, subIt graph.Iterator) graph.Iterator { - and := iterator.NewAnd() + func(qs graph.QuadStore, subIt graph.Iterator) graph.Iterator { + and := iterator.NewAnd(qs) and.AddSubIterator(it) and.AddSubIterator(subIt) return and @@ -255,7 +255,7 @@ func andMorphism(p *Path) morphism { func() morphism { return andMorphism(p) }, func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { subIt := p.BuildIteratorOn(qs) - and := iterator.NewAnd() + and := iterator.NewAnd(qs) and.AddSubIterator(it) and.AddSubIterator(subIt) return and @@ -294,7 +294,7 @@ func exceptMorphism(p *Path) morphism { func(qs graph.QuadStore, base graph.Iterator) graph.Iterator { subIt := p.BuildIteratorOn(qs) notIt := iterator.NewNot(subIt, qs.NodesAllIterator()) - and := iterator.NewAnd() + and := iterator.NewAnd(qs) and.AddSubIterator(base) and.AddSubIterator(notIt) return and @@ -308,7 +308,7 @@ func inOutIterator(viaPath *Path, it graph.Iterator, reverse bool) graph.Iterato in, out = out, in } lto := iterator.NewLinksTo(viaPath.qs, it, in) - and := iterator.NewAnd() + and := iterator.NewAnd(viaPath.qs) and.AddSubIterator(iterator.NewLinksTo(viaPath.qs, viaPath.BuildIterator(), quad.Predicate)) and.AddSubIterator(lto) return iterator.NewHasA(viaPath.qs, and, out) diff --git a/graph/quadwriter.go b/graph/quadwriter.go index aaaf05f..dc42ac5 100644 --- a/graph/quadwriter.go +++ b/graph/quadwriter.go @@ -45,8 +45,8 @@ type Delta struct { } type Handle struct { - QuadStore QuadStore - QuadWriter QuadWriter + QuadStore + QuadWriter } type IgnoreOpts struct { diff --git a/imports.go b/imports.go new file mode 100644 index 0000000..6b442f6 --- /dev/null +++ b/imports.go @@ -0,0 +1,44 @@ +package cayley + +import ( + "github.com/google/cayley/graph" + _ "github.com/google/cayley/graph/memstore" + "github.com/google/cayley/graph/path" + "github.com/google/cayley/quad" + _ "github.com/google/cayley/writer" +) + +type Iterator graph.Iterator +type QuadStore graph.QuadStore +type QuadWriter graph.QuadWriter + +type Path path.Path + +type Quad quad.Quad + +var StartMorphism = path.StartMorphism +var StartPath = path.StartPath + +var RawNext = graph.Next + +type Handle struct { + graph.QuadStore + graph.QuadWriter +} + +func NewMemoryGraph() (*Handle, error) { + qs, err := graph.NewQuadStore("memstore", "", nil) + if err != nil { + return nil, err + } + qw, err := graph.NewQuadWriter("single", qs, nil) + if err != nil { + return nil, err + } + return &Handle{qs, qw}, nil +} + +func (h *Handle) Close() { + h.QuadStore.Close() + h.QuadWriter.Close() +} From 2356ba4a643e3afe8e3a85d43f65d7d4ad514fe8 Mon Sep 17 00:00:00 2001 From: Barak Michener Date: Fri, 1 May 2015 18:53:37 -0400 Subject: [PATCH 13/13] export Quad via a function --- imports.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/imports.go b/imports.go index 6b442f6..a18dec5 100644 --- a/imports.go +++ b/imports.go @@ -14,8 +14,6 @@ type QuadWriter graph.QuadWriter type Path path.Path -type Quad quad.Quad - var StartMorphism = path.StartMorphism var StartPath = path.StartPath @@ -26,6 +24,10 @@ type Handle struct { graph.QuadWriter } +func Quad(subject, predicate, object, label string) quad.Quad { + return quad.Quad{subject, predicate, object, label} +} + func NewMemoryGraph() (*Handle, error) { qs, err := graph.NewQuadStore("memstore", "", nil) if err != nil {