244 lines
6.6 KiB
Go
244 lines
6.6 KiB
Go
// 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 iterator
|
|
|
|
// Defines one of the base iterators, the LinksTo iterator. A LinksTo takes a
|
|
// subiterator of nodes, and contains an iteration of links which "link to"
|
|
// those nodes in a given direction.
|
|
//
|
|
// Next()ing a LinksTo is straightforward -- iterate through all links to //
|
|
// things in the subiterator, and then advance the subiterator, and do it again.
|
|
// LinksTo is therefore sensitive to growing with a fanout. (A small-sized
|
|
// subiterator could cause LinksTo to be large).
|
|
//
|
|
// Contains()ing a LinksTo means, given a link, take the direction we care about
|
|
// and check if it's in our subiterator. Checking is therefore fairly cheap, and
|
|
// similar to checking the subiterator alone.
|
|
//
|
|
// Can be seen as the dual of the HasA iterator.
|
|
|
|
import (
|
|
"github.com/google/cayley/graph"
|
|
"github.com/google/cayley/quad"
|
|
)
|
|
|
|
// A LinksTo has a reference back to the graph.QuadStore (to create the iterators
|
|
// for each node) the subiterator, and the direction the iterator comes from.
|
|
// `next_it` is the tempoarary iterator held per result in `primary_it`.
|
|
type LinksTo struct {
|
|
uid uint64
|
|
tags graph.Tagger
|
|
qs graph.QuadStore
|
|
primaryIt graph.Iterator
|
|
dir quad.Direction
|
|
nextIt graph.Iterator
|
|
result graph.Value
|
|
runstats graph.IteratorStats
|
|
err error
|
|
}
|
|
|
|
// Construct a new LinksTo iterator around a direction and a subiterator of
|
|
// nodes.
|
|
func NewLinksTo(qs graph.QuadStore, it graph.Iterator, d quad.Direction) *LinksTo {
|
|
return &LinksTo{
|
|
uid: NextUID(),
|
|
qs: qs,
|
|
primaryIt: it,
|
|
dir: d,
|
|
nextIt: &Null{},
|
|
}
|
|
}
|
|
|
|
func (it *LinksTo) UID() uint64 {
|
|
return it.uid
|
|
}
|
|
|
|
func (it *LinksTo) Reset() {
|
|
it.primaryIt.Reset()
|
|
if it.nextIt != nil {
|
|
it.nextIt.Close()
|
|
}
|
|
it.nextIt = &Null{}
|
|
}
|
|
|
|
func (it *LinksTo) Tagger() *graph.Tagger {
|
|
return &it.tags
|
|
}
|
|
|
|
func (it *LinksTo) Clone() graph.Iterator {
|
|
out := NewLinksTo(it.qs, it.primaryIt.Clone(), it.dir)
|
|
out.tags.CopyFrom(it)
|
|
return out
|
|
}
|
|
|
|
// Return the direction under consideration.
|
|
func (it *LinksTo) Direction() quad.Direction { return it.dir }
|
|
|
|
// Tag these results, and our subiterator's results.
|
|
func (it *LinksTo) TagResults(dst map[string]graph.Value) {
|
|
for _, tag := range it.tags.Tags() {
|
|
dst[tag] = it.Result()
|
|
}
|
|
|
|
for tag, value := range it.tags.Fixed() {
|
|
dst[tag] = value
|
|
}
|
|
|
|
it.primaryIt.TagResults(dst)
|
|
}
|
|
|
|
// DEPRECATED
|
|
func (it *LinksTo) ResultTree() *graph.ResultTree {
|
|
tree := graph.NewResultTree(it.Result())
|
|
tree.AddSubtree(it.primaryIt.ResultTree())
|
|
return tree
|
|
}
|
|
|
|
func (it *LinksTo) Describe() graph.Description {
|
|
primary := it.primaryIt.Describe()
|
|
return graph.Description{
|
|
UID: it.UID(),
|
|
Type: it.Type(),
|
|
Direction: it.dir,
|
|
Iterator: &primary,
|
|
}
|
|
}
|
|
|
|
// If it checks in the right direction for the subiterator, it is a valid link
|
|
// for the LinksTo.
|
|
func (it *LinksTo) Contains(val graph.Value) bool {
|
|
graph.ContainsLogIn(it, val)
|
|
it.runstats.Contains += 1
|
|
node := it.qs.QuadDirection(val, it.dir)
|
|
if it.primaryIt.Contains(node) {
|
|
it.result = val
|
|
return graph.ContainsLogOut(it, val, true)
|
|
}
|
|
it.err = it.primaryIt.Err()
|
|
return graph.ContainsLogOut(it, val, false)
|
|
}
|
|
|
|
// Return a list containing only our subiterator.
|
|
func (it *LinksTo) SubIterators() []graph.Iterator {
|
|
return []graph.Iterator{it.primaryIt}
|
|
}
|
|
|
|
// Optimize the LinksTo, by replacing it if it can be.
|
|
func (it *LinksTo) Optimize() (graph.Iterator, bool) {
|
|
newPrimary, changed := it.primaryIt.Optimize()
|
|
if changed {
|
|
it.primaryIt = newPrimary
|
|
if it.primaryIt.Type() == graph.Null {
|
|
it.nextIt.Close()
|
|
return it.primaryIt, true
|
|
}
|
|
}
|
|
// Ask the graph.QuadStore if we can be replaced. Often times, this is a great
|
|
// optimization opportunity (there's a fixed iterator underneath us, for
|
|
// example).
|
|
newReplacement, hasOne := it.qs.OptimizeIterator(it)
|
|
if hasOne {
|
|
it.Close()
|
|
return newReplacement, true
|
|
}
|
|
return it, false
|
|
}
|
|
|
|
// Next()ing a LinksTo operates as described above.
|
|
func (it *LinksTo) Next() bool {
|
|
graph.NextLogIn(it)
|
|
it.runstats.Next += 1
|
|
if graph.Next(it.nextIt) {
|
|
it.runstats.ContainsNext += 1
|
|
it.result = it.nextIt.Result()
|
|
return graph.NextLogOut(it, it.nextIt, true)
|
|
}
|
|
|
|
// If there's an error in the 'next' iterator, we save it and we're done.
|
|
it.err = it.nextIt.Err()
|
|
if it.err != nil {
|
|
return false
|
|
}
|
|
|
|
// Subiterator is empty, get another one
|
|
if !graph.Next(it.primaryIt) {
|
|
// Possibly save error
|
|
it.err = it.primaryIt.Err()
|
|
|
|
// We're out of nodes in our subiterator, so we're done as well.
|
|
return graph.NextLogOut(it, 0, false)
|
|
}
|
|
it.nextIt.Close()
|
|
it.nextIt = it.qs.QuadIterator(it.dir, it.primaryIt.Result())
|
|
|
|
// Recurse -- return the first in the next set.
|
|
return it.Next()
|
|
}
|
|
|
|
func (it *LinksTo) Err() error {
|
|
return it.err
|
|
}
|
|
|
|
func (it *LinksTo) Result() graph.Value {
|
|
return it.result
|
|
}
|
|
|
|
// Close our subiterators. It closes all subiterators it can, but
|
|
// returns the first error it encounters.
|
|
func (it *LinksTo) Close() error {
|
|
err := it.nextIt.Close()
|
|
|
|
err2 := it.primaryIt.Close()
|
|
if err2 != nil && err == nil {
|
|
err = err2
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// We won't ever have a new result, but our subiterators might.
|
|
func (it *LinksTo) NextPath() bool {
|
|
ok := it.primaryIt.NextPath()
|
|
if !ok {
|
|
it.err = it.primaryIt.Err()
|
|
}
|
|
return ok
|
|
}
|
|
|
|
// Register the LinksTo.
|
|
func (it *LinksTo) Type() graph.Type { return graph.LinksTo }
|
|
|
|
// Return a guess as to how big or costly it is to next the iterator.
|
|
func (it *LinksTo) Stats() graph.IteratorStats {
|
|
subitStats := it.primaryIt.Stats()
|
|
// TODO(barakmich): These should really come from the quadstore itself
|
|
fanoutFactor := int64(20)
|
|
checkConstant := int64(1)
|
|
nextConstant := int64(2)
|
|
return graph.IteratorStats{
|
|
NextCost: nextConstant + subitStats.NextCost,
|
|
ContainsCost: checkConstant + subitStats.ContainsCost,
|
|
Size: fanoutFactor * subitStats.Size,
|
|
Next: it.runstats.Next,
|
|
Contains: it.runstats.Contains,
|
|
ContainsNext: it.runstats.ContainsNext,
|
|
}
|
|
}
|
|
|
|
func (it *LinksTo) Size() (int64, bool) {
|
|
return it.Stats().Size, false
|
|
}
|
|
|
|
var _ graph.Nexter = &LinksTo{}
|