From f825fef208159ea069804b25162a7c74560d1b0e Mon Sep 17 00:00:00 2001 From: Barak Michener Date: Tue, 2 Jun 2015 15:27:12 -0400 Subject: [PATCH] Add Save/SaveReverse and HAS API and split path.go into two files --- graph/path/morphism_apply_functions.go | 262 +++++++++++++++++++++++++++++++++ graph/path/path.go | 197 ++++--------------------- graph/path/path_test.go | 22 +++ 3 files changed, 316 insertions(+), 165 deletions(-) create mode 100644 graph/path/morphism_apply_functions.go diff --git a/graph/path/morphism_apply_functions.go b/graph/path/morphism_apply_functions.go new file mode 100644 index 0000000..c76c3d3 --- /dev/null +++ b/graph/path/morphism_apply_functions.go @@ -0,0 +1,262 @@ +// 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" +) + +func isMorphism(nodes ...string) morphism { + return morphism{ + Name: "is", + Reversal: func() morphism { return isMorphism(nodes...) }, + Apply: 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(qs) + and.AddSubIterator(sub) + and.AddSubIterator(it) + return and + }, + } +} + +func hasMorphism(via interface{}, nodes ...string) morphism { + return morphism{ + Name: "has", + Reversal: func() morphism { return hasMorphism(via, nodes...) }, + Apply: 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 + } + var viaPath *Path + if via != nil { + viaPath = buildViaPath(qs, via) + } else { + viaPath = buildViaPath(qs) + } + subAnd := iterator.NewAnd(qs) + subAnd.AddSubIterator(iterator.NewLinksTo(qs, sub, quad.Object)) + subAnd.AddSubIterator(iterator.NewLinksTo(qs, viaPath.BuildIterator(), quad.Predicate)) + hasa := iterator.NewHasA(qs, subAnd, quad.Subject) + and := iterator.NewAnd(qs) + and.AddSubIterator(it) + and.AddSubIterator(hasa) + return and + }, + } +} + +func tagMorphism(tags ...string) morphism { + return morphism{ + Name: "tag", + Reversal: func() morphism { return tagMorphism(tags...) }, + Apply: func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { + for _, t := range tags { + it.Tagger().Add(t) + } + return it + }, + tags: tags, + } +} + +func outMorphism(via ...interface{}) morphism { + return morphism{ + Name: "out", + Reversal: func() morphism { return inMorphism(via...) }, + Apply: func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { + path := buildViaPath(qs, via...) + return inOutIterator(path, it, false) + }, + } +} + +func inMorphism(via ...interface{}) morphism { + return morphism{ + Name: "in", + Reversal: func() morphism { return outMorphism(via...) }, + Apply: func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { + path := buildViaPath(qs, via...) + return inOutIterator(path, it, true) + }, + } +} + +func iteratorMorphism(it graph.Iterator) morphism { + return morphism{ + Name: "iterator", + Reversal: func() morphism { return iteratorMorphism(it) }, + Apply: func(qs graph.QuadStore, subIt graph.Iterator) graph.Iterator { + and := iterator.NewAnd(qs) + and.AddSubIterator(it) + and.AddSubIterator(subIt) + return and + }, + } +} + +func andMorphism(p *Path) morphism { + return morphism{ + Name: "and", + Reversal: func() morphism { return andMorphism(p) }, + Apply: func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { + subIt := p.BuildIteratorOn(qs) + and := iterator.NewAnd(qs) + and.AddSubIterator(it) + and.AddSubIterator(subIt) + return and + }, + } +} + +func orMorphism(p *Path) morphism { + return morphism{ + Name: "or", + Reversal: func() morphism { return orMorphism(p) }, + Apply: func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { + subIt := p.BuildIteratorOn(qs) + and := iterator.NewOr() + and.AddSubIterator(it) + and.AddSubIterator(subIt) + return and + }, + } +} + +func followMorphism(p *Path) morphism { + return morphism{ + Name: "follow", + Reversal: func() morphism { return followMorphism(p.Reverse()) }, + Apply: func(qs graph.QuadStore, base graph.Iterator) graph.Iterator { + return p.Morphism()(qs, base) + }, + } +} + +func exceptMorphism(p *Path) morphism { + return morphism{ + Name: "except", + Reversal: func() morphism { return exceptMorphism(p) }, + Apply: func(qs graph.QuadStore, base graph.Iterator) graph.Iterator { + subIt := p.BuildIteratorOn(qs) + notIt := iterator.NewNot(subIt, qs.NodesAllIterator()) + and := iterator.NewAnd(qs) + and.AddSubIterator(base) + and.AddSubIterator(notIt) + return and + }, + } +} + +func saveMorphism(via interface{}, tag string) morphism { + return morphism{ + Name: "save", + Reversal: func() morphism { return saveMorphism(via, tag) }, + Apply: func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { + return buildSave(qs, via, tag, it, false) + }, + tags: []string{tag}, + } +} + +func saveReverseMorphism(via interface{}, tag string) morphism { + return morphism{ + Name: "saver", + Reversal: func() morphism { return saveReverseMorphism(via, tag) }, + Apply: func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { + return buildSave(qs, via, tag, it, true) + }, + tags: []string{tag}, + } +} + +func buildSave(qs graph.QuadStore, via interface{}, tag string, it graph.Iterator, reverse bool) graph.Iterator { + all := qs.NodesAllIterator() + all.Tagger().Add(tag) + node, allDir := quad.Subject, quad.Object + var viaPath *Path + if via != nil { + viaPath = buildViaPath(qs, via) + } else { + viaPath = buildViaPath(qs) + } + if reverse { + node, allDir = allDir, node + } + lto := iterator.NewLinksTo(qs, all, allDir) + subAnd := iterator.NewAnd(qs) + subAnd.AddSubIterator(iterator.NewLinksTo(qs, viaPath.BuildIterator(), quad.Predicate)) + subAnd.AddSubIterator(lto) + hasa := iterator.NewHasA(qs, subAnd, node) + and := iterator.NewAnd(qs) + and.AddSubIterator(hasa) + and.AddSubIterator(it) + 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(viaPath.qs) + 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] + switch p := v.(type) { + case *Path: + return p + case string: + return StartPath(qs, p) + default: + 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.go b/graph/path/path.go index 547fb2b..143e3d6 100644 --- a/graph/path/path.go +++ b/graph/path/path.go @@ -14,16 +14,13 @@ package path -import ( - "github.com/google/cayley/graph" - "github.com/google/cayley/graph/iterator" - "github.com/google/cayley/quad" -) +import "github.com/google/cayley/graph" type morphism struct { Name string Reversal func() morphism Apply graph.ApplyMorphism + tags []string } // Path represents either a morphism (a pre-defined path stored for later use), @@ -76,11 +73,15 @@ func (p *Path) Reverse() *Path { return newPath } +// Is declares that the current nodes in this path are only the nodes +// passed as arguments. func (p *Path) Is(nodes ...string) *Path { p.stack = append(p.stack, isMorphism(nodes...)) return p } +// Tag adds tag strings to the nodes at this point in the path for each result +// path in the set. func (p *Path) Tag(tags ...string) *Path { p.stack = append(p.stack, tagMorphism(tags...)) return p @@ -149,6 +150,32 @@ func (p *Path) FollowReverse(path *Path) *Path { return p } +// Save will, from the current nodes in the path, retrieve the node +// one linkage away (given by either a path or a predicate), add the given +// tag, and propagate that to the result set. +// +// For example: +// // Will return []map[string]string{{"social_status: "cool"}} +// StartPath(qs, "B").Save("status", "social_status" +func (p *Path) Save(via interface{}, tag string) *Path { + p.stack = append(p.stack, saveMorphism(via, tag)) + return p +} + +// SaveReverse is the same as Save, only in the reverse direction +// (the subject of the linkage should be tagged, instead of the object) +func (p *Path) SaveReverse(via interface{}, tag string) *Path { + p.stack = append(p.stack, saveReverseMorphism(via, tag)) + return p +} + +// Has limits the paths to be ones where the current nodes have some linkage +// to some known node. +func (p *Path) Has(via interface{}, nodes ...string) *Path { + p.stack = append(p.stack, hasMorphism(via, nodes...)) + 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 @@ -178,163 +205,3 @@ func (p *Path) Morphism() graph.ApplyMorphism { return i } } - -func isMorphism(nodes ...string) morphism { - return morphism{ - "is", - 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(qs) - and.AddSubIterator(sub) - and.AddSubIterator(it) - return and - }, - } -} - -func tagMorphism(tags ...string) morphism { - return morphism{ - "tag", - func() morphism { return tagMorphism(tags...) }, - func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { - for _, t := range tags { - it.Tagger().Add(t) - } - return it - }} -} - -func outMorphism(via ...interface{}) morphism { - return morphism{ - "out", - 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(via ...interface{}) morphism { - return morphism{ - "in", - func() morphism { return outMorphism(via...) }, - func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { - path := buildViaPath(qs, via...) - return inOutIterator(path, it, true) - }, - } -} - -func iteratorMorphism(it graph.Iterator) morphism { - return morphism{ - "iterator", - func() morphism { return iteratorMorphism(it) }, - func(qs graph.QuadStore, subIt graph.Iterator) graph.Iterator { - and := iterator.NewAnd(qs) - and.AddSubIterator(it) - and.AddSubIterator(subIt) - return and - }, - } -} - -func andMorphism(p *Path) morphism { - return morphism{ - "and", - func() morphism { return andMorphism(p) }, - func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { - subIt := p.BuildIteratorOn(qs) - and := iterator.NewAnd(qs) - and.AddSubIterator(it) - and.AddSubIterator(subIt) - return and - }, - } -} - -func orMorphism(p *Path) morphism { - return morphism{ - "or", - func() morphism { return orMorphism(p) }, - func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { - subIt := p.BuildIteratorOn(qs) - and := iterator.NewOr() - and.AddSubIterator(it) - and.AddSubIterator(subIt) - return and - }, - } -} - -func followMorphism(p *Path) morphism { - return morphism{ - "follow", - func() morphism { return followMorphism(p.Reverse()) }, - func(qs graph.QuadStore, base graph.Iterator) graph.Iterator { - return p.Morphism()(qs, base) - }, - } -} - -func exceptMorphism(p *Path) morphism { - return morphism{ - "except", - func() morphism { return exceptMorphism(p) }, - func(qs graph.QuadStore, base graph.Iterator) graph.Iterator { - subIt := p.BuildIteratorOn(qs) - notIt := iterator.NewNot(subIt, qs.NodesAllIterator()) - and := iterator.NewAnd(qs) - 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(viaPath.qs) - 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] - switch v := v.(type) { - case *Path: - return v - case string: - return StartPath(qs, v) - default: - 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 index 87419f9..6fb3362 100644 --- a/graph/path/path_test.go +++ b/graph/path/path_test.go @@ -160,6 +160,28 @@ func testSet(qs graph.QuadStore) []test { path: StartPath(qs, "A", "B", "C").Except(StartPath(qs, "B")).Except(StartPath(qs, "A")), expect: []string{"C"}, }, + { + message: "show a simple save", + path: StartPath(qs).Save("status", "somecool"), + tag: "somecool", + expect: []string{"cool", "cool", "cool"}, + }, + { + message: "show a simple saveR", + path: StartPath(qs, "cool").SaveReverse("status", "who"), + tag: "who", + expect: []string{"G", "D", "B"}, + }, + { + message: "show a simple Has", + path: StartPath(qs).Has("status", "cool"), + expect: []string{"G", "D", "B"}, + }, + { + message: "show a double Has", + path: StartPath(qs).Has("status", "cool").Has("follows", "F"), + expect: []string{"B"}, + }, } }