https://github.com/google/cayley
Tip revision: 6fb42084bad95ecdb98a4bf9eb354c82558c7506 authored by 3pCode on 07 January 2019, 16:42:23 UTC
Merge pull request #1 from cayleygraph/master
Merge pull request #1 from cayleygraph/master
Tip revision: 6fb4208
linksto.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 (
"context"
"fmt"
"github.com/cayleygraph/cayley/graph"
"github.com/cayleygraph/cayley/quad"
)
var _ graph.Iterator = &LinksTo{}
// 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)
out.runstats.Size, out.runstats.ExactSize = it.runstats.Size, it.runstats.ExactSize
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) {
it.tags.TagResult(dst, it.Result())
it.primaryIt.TagResults(dst)
}
func (it *LinksTo) String() string {
return fmt.Sprintf("LinksTo(%v)", it.dir)
}
// If it checks in the right direction for the subiterator, it is a valid link
// for the LinksTo.
func (it *LinksTo) Contains(ctx context.Context, val graph.Value) bool {
graph.ContainsLogIn(it, val)
it.runstats.Contains += 1
node := it.qs.QuadDirection(val, it.dir)
if it.primaryIt.Contains(ctx, 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(ctx context.Context) bool {
for {
graph.NextLogIn(it)
it.runstats.Next += 1
if it.nextIt.Next(ctx) {
it.runstats.ContainsNext += 1
it.result = it.nextIt.Result()
return graph.NextLogOut(it, 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 !it.primaryIt.Next(ctx) {
// 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, false)
}
it.nextIt.Close()
it.nextIt = it.qs.QuadIterator(it.dir, it.primaryIt.Result())
// Continue -- return the first in the next set.
}
}
func (it *LinksTo) Err() error {
return it.err
}
func (it *LinksTo) Result() graph.Value {
return it.result
}
// Close closes the iterator. It closes all subiterators it can, but
// returns the first error it encounters.
func (it *LinksTo) Close() error {
err := it.nextIt.Close()
_err := it.primaryIt.Close()
if _err != nil && err == nil {
err = _err
}
return err
}
// We won't ever have a new result, but our subiterators might.
func (it *LinksTo) NextPath(ctx context.Context) bool {
ok := it.primaryIt.NextPath(ctx)
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
checkConstant := int64(1)
nextConstant := int64(2)
st := graph.IteratorStats{
NextCost: nextConstant + subitStats.NextCost,
ContainsCost: checkConstant + subitStats.ContainsCost,
Next: it.runstats.Next,
Contains: it.runstats.Contains,
ContainsNext: it.runstats.ContainsNext,
}
st.Size, st.ExactSize = it.Size()
return st
}
func (it *LinksTo) Size() (int64, bool) {
if it.runstats.Size != 0 {
return it.runstats.Size, it.runstats.ExactSize
}
if fixed, ok := it.primaryIt.(*Fixed); ok {
// get real sizes from sub iterators
var (
sz int64
exact = true
)
for _, v := range fixed.Values() {
sit := it.qs.QuadIterator(it.dir, v)
n, ex := sit.Size()
sit.Close()
sz += n
exact = exact && ex
}
it.runstats.Size, it.runstats.ExactSize = sz, exact
return sz, exact
}
// TODO(barakmich): It should really come from the quadstore itself
const fanoutFactor = 20
sz, _ := it.primaryIt.Size()
sz *= fanoutFactor
it.runstats.Size, it.runstats.ExactSize = sz, false
return sz, false
}