github.com/cayleygraph/cayley@v0.7.7/graph/iterator/linksto.go (about)

     1  // Copyright 2014 The Cayley Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package iterator
    16  
    17  // Defines one of the base iterators, the LinksTo iterator. A LinksTo takes a
    18  // subiterator of nodes, and contains an iteration of links which "link to"
    19  // those nodes in a given direction.
    20  //
    21  // Next()ing a LinksTo is straightforward -- iterate through all links to //
    22  // things in the subiterator, and then advance the subiterator, and do it again.
    23  // LinksTo is therefore sensitive to growing with a fanout. (A small-sized
    24  // subiterator could cause LinksTo to be large).
    25  //
    26  // Contains()ing a LinksTo means, given a link, take the direction we care about
    27  // and check if it's in our subiterator. Checking is therefore fairly cheap, and
    28  // similar to checking the subiterator alone.
    29  //
    30  // Can be seen as the dual of the HasA iterator.
    31  
    32  import (
    33  	"context"
    34  	"fmt"
    35  
    36  	"github.com/cayleygraph/cayley/graph"
    37  	"github.com/cayleygraph/quad"
    38  )
    39  
    40  var _ graph.IteratorFuture = &LinksTo{}
    41  
    42  // A LinksTo has a reference back to the graph.QuadStore (to create the iterators
    43  // for each node) the subiterator, and the direction the iterator comes from.
    44  // `next_it` is the tempoarary iterator held per result in `primary_it`.
    45  type LinksTo struct {
    46  	it *linksTo
    47  	graph.Iterator
    48  }
    49  
    50  // Construct a new LinksTo iterator around a direction and a subiterator of
    51  // nodes.
    52  func NewLinksTo(qs graph.QuadIndexer, sub graph.Iterator, d quad.Direction) *LinksTo {
    53  	it := &LinksTo{
    54  		it: newLinksTo(qs, graph.AsShape(sub), d),
    55  	}
    56  	it.Iterator = graph.NewLegacy(it.it, it)
    57  	return it
    58  }
    59  
    60  func (it *LinksTo) AsShape() graph.IteratorShape {
    61  	it.Close()
    62  	return it.it
    63  }
    64  
    65  // Return the direction under consideration.
    66  func (it *LinksTo) Direction() quad.Direction { return it.it.Direction() }
    67  
    68  var _ graph.IteratorShapeCompat = &linksTo{}
    69  
    70  // A LinksTo has a reference back to the graph.QuadStore (to create the iterators
    71  // for each node) the subiterator, and the direction the iterator comes from.
    72  // `next_it` is the tempoarary iterator held per result in `primary_it`.
    73  type linksTo struct {
    74  	qs      graph.QuadIndexer
    75  	primary graph.IteratorShape
    76  	dir     quad.Direction
    77  	size    graph.Size
    78  }
    79  
    80  // Construct a new LinksTo iterator around a direction and a subiterator of
    81  // nodes.
    82  func newLinksTo(qs graph.QuadIndexer, it graph.IteratorShape, d quad.Direction) *linksTo {
    83  	return &linksTo{
    84  		qs:      qs,
    85  		primary: it,
    86  		dir:     d,
    87  	}
    88  }
    89  
    90  // Return the direction under consideration.
    91  func (it *linksTo) Direction() quad.Direction { return it.dir }
    92  
    93  func (it *linksTo) Iterate() graph.Scanner {
    94  	return newLinksToNext(it.qs, it.primary.Iterate(), it.dir)
    95  }
    96  
    97  func (it *linksTo) Lookup() graph.Index {
    98  	return newLinksToContains(it.qs, it.primary.Lookup(), it.dir)
    99  }
   100  
   101  func (it *linksTo) AsLegacy() graph.Iterator {
   102  	it2 := &LinksTo{it: it}
   103  	it2.Iterator = graph.NewLegacy(it, it2)
   104  	return it2
   105  }
   106  
   107  func (it *linksTo) String() string {
   108  	return fmt.Sprintf("LinksTo(%v)", it.dir)
   109  }
   110  
   111  // Return a list containing only our subiterator.
   112  func (it *linksTo) SubIterators() []graph.IteratorShape {
   113  	return []graph.IteratorShape{it.primary}
   114  }
   115  
   116  // Optimize the LinksTo, by replacing it if it can be.
   117  func (it *linksTo) Optimize(ctx context.Context) (graph.IteratorShape, bool) {
   118  	newPrimary, changed := it.primary.Optimize(ctx)
   119  	if changed {
   120  		it.primary = newPrimary
   121  		if IsNull2(it.primary) {
   122  			return it.primary, true
   123  		}
   124  	}
   125  	return it, false
   126  }
   127  
   128  // Return a guess as to how big or costly it is to next the iterator.
   129  func (it *linksTo) Stats(ctx context.Context) (graph.IteratorCosts, error) {
   130  	subitStats, err := it.primary.Stats(ctx)
   131  	// TODO(barakmich): These should really come from the quadstore itself
   132  	checkConstant := int64(1)
   133  	nextConstant := int64(2)
   134  	return graph.IteratorCosts{
   135  		NextCost:     nextConstant + subitStats.NextCost,
   136  		ContainsCost: checkConstant + subitStats.ContainsCost,
   137  		Size:         it.getSize(ctx),
   138  	}, err
   139  }
   140  
   141  func (it *linksTo) getSize(ctx context.Context) graph.Size {
   142  	if it.size.Size != 0 {
   143  		return it.size
   144  	}
   145  	if fixed, ok := graph.AsLegacy(it.primary).(*Fixed); ok {
   146  		// get real sizes from sub iterators
   147  		var (
   148  			sz    int64
   149  			exact = true
   150  		)
   151  		for _, v := range fixed.Values() {
   152  			sit := it.qs.QuadIterator(it.dir, v)
   153  			n, ex := sit.Size()
   154  			sit.Close()
   155  			sz += n
   156  			exact = exact && ex
   157  		}
   158  		it.size.Size, it.size.Exact = sz, exact
   159  		return it.size
   160  	}
   161  	// TODO(barakmich): It should really come from the quadstore itself
   162  	const fanoutFactor = 20
   163  	st, _ := it.primary.Stats(ctx)
   164  	st.Size.Size *= fanoutFactor
   165  	it.size.Size, it.size.Exact = st.Size.Size, false
   166  	return it.size
   167  }
   168  
   169  // A LinksTo has a reference back to the graph.QuadStore (to create the iterators
   170  // for each node) the subiterator, and the direction the iterator comes from.
   171  // `next_it` is the tempoarary iterator held per result in `primary_it`.
   172  type linksToNext struct {
   173  	qs      graph.QuadIndexer
   174  	primary graph.Scanner
   175  	dir     quad.Direction
   176  	nextIt  graph.Scanner
   177  	result  graph.Ref
   178  	err     error
   179  }
   180  
   181  // Construct a new LinksTo iterator around a direction and a subiterator of
   182  // nodes.
   183  func newLinksToNext(qs graph.QuadIndexer, it graph.Scanner, d quad.Direction) graph.Scanner {
   184  	return &linksToNext{
   185  		qs:      qs,
   186  		primary: it,
   187  		dir:     d,
   188  		nextIt:  newNull().Iterate(),
   189  	}
   190  }
   191  
   192  // Return the direction under consideration.
   193  func (it *linksToNext) Direction() quad.Direction { return it.dir }
   194  
   195  // Tag these results, and our subiterator's results.
   196  func (it *linksToNext) TagResults(dst map[string]graph.Ref) {
   197  	it.primary.TagResults(dst)
   198  }
   199  
   200  func (it *linksToNext) String() string {
   201  	return fmt.Sprintf("LinksToNext(%v)", it.dir)
   202  }
   203  
   204  // Next()ing a LinksTo operates as described above.
   205  func (it *linksToNext) Next(ctx context.Context) bool {
   206  	for {
   207  		if it.nextIt.Next(ctx) {
   208  			it.result = it.nextIt.Result()
   209  			return true
   210  		}
   211  
   212  		// If there's an error in the 'next' iterator, we save it and we're done.
   213  		it.err = it.nextIt.Err()
   214  		if it.err != nil {
   215  			return false
   216  		}
   217  
   218  		// Subiterator is empty, get another one
   219  		if !it.primary.Next(ctx) {
   220  			// Possibly save error
   221  			it.err = it.primary.Err()
   222  
   223  			// We're out of nodes in our subiterator, so we're done as well.
   224  			return false
   225  		}
   226  		it.nextIt.Close()
   227  		it.nextIt = it.qs.QuadIterator(it.dir, it.primary.Result())
   228  
   229  		// Continue -- return the first in the next set.
   230  	}
   231  }
   232  
   233  func (it *linksToNext) Err() error {
   234  	return it.err
   235  }
   236  
   237  func (it *linksToNext) Result() graph.Ref {
   238  	return it.result
   239  }
   240  
   241  // Close closes the iterator.  It closes all subiterators it can, but
   242  // returns the first error it encounters.
   243  func (it *linksToNext) Close() error {
   244  	err := it.nextIt.Close()
   245  
   246  	_err := it.primary.Close()
   247  	if _err != nil && err == nil {
   248  		err = _err
   249  	}
   250  
   251  	return err
   252  }
   253  
   254  // We won't ever have a new result, but our subiterators might.
   255  func (it *linksToNext) NextPath(ctx context.Context) bool {
   256  	ok := it.primary.NextPath(ctx)
   257  	if !ok {
   258  		it.err = it.primary.Err()
   259  	}
   260  	return ok
   261  }
   262  
   263  // A LinksTo has a reference back to the graph.QuadStore (to create the iterators
   264  // for each node) the subiterator, and the direction the iterator comes from.
   265  // `next_it` is the tempoarary iterator held per result in `primary_it`.
   266  type linksToContains struct {
   267  	qs      graph.QuadIndexer
   268  	primary graph.Index
   269  	dir     quad.Direction
   270  	result  graph.Ref
   271  }
   272  
   273  // Construct a new LinksTo iterator around a direction and a subiterator of
   274  // nodes.
   275  func newLinksToContains(qs graph.QuadIndexer, it graph.Index, d quad.Direction) graph.Index {
   276  	return &linksToContains{
   277  		qs:      qs,
   278  		primary: it,
   279  		dir:     d,
   280  	}
   281  }
   282  
   283  // Return the direction under consideration.
   284  func (it *linksToContains) Direction() quad.Direction { return it.dir }
   285  
   286  // Tag these results, and our subiterator's results.
   287  func (it *linksToContains) TagResults(dst map[string]graph.Ref) {
   288  	it.primary.TagResults(dst)
   289  }
   290  
   291  func (it *linksToContains) String() string {
   292  	return fmt.Sprintf("LinksToContains(%v)", it.dir)
   293  }
   294  
   295  // If it checks in the right direction for the subiterator, it is a valid link
   296  // for the LinksTo.
   297  func (it *linksToContains) Contains(ctx context.Context, val graph.Ref) bool {
   298  	node := it.qs.QuadDirection(val, it.dir)
   299  	if it.primary.Contains(ctx, node) {
   300  		it.result = val
   301  		return true
   302  	}
   303  	return false
   304  }
   305  
   306  func (it *linksToContains) Err() error {
   307  	return it.primary.Err()
   308  }
   309  
   310  func (it *linksToContains) Result() graph.Ref {
   311  	return it.result
   312  }
   313  
   314  // Close closes the iterator.  It closes all subiterators it can, but
   315  // returns the first error it encounters.
   316  func (it *linksToContains) Close() error {
   317  	return it.primary.Close()
   318  }
   319  
   320  // We won't ever have a new result, but our subiterators might.
   321  func (it *linksToContains) NextPath(ctx context.Context) bool {
   322  	return it.primary.NextPath(ctx)
   323  }