github.com/cayleygraph/cayley@v0.7.7/graph/iterator/hasa.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 HasA iterator. The HasA takes a
    18  // subiterator of links, and acts as an iterator of nodes in the given
    19  // direction. The name comes from the idea that a "link HasA subject" or a "link
    20  // HasA predicate".
    21  //
    22  // HasA is weird in that it may return the same value twice if on the Next()
    23  // path. That's okay -- in reality, it can be viewed as returning the value for
    24  // a new quad, but to make logic much simpler, here we have the HasA.
    25  //
    26  // Likewise, it's important to think about Contains()ing a HasA. When given a
    27  // value to check, it means "Check all predicates that have this value for your
    28  // direction against the subiterator." This would imply that there's more than
    29  // one possibility for the same Contains()ed value. While we could return the
    30  // number of options, it's simpler to return one, and then call NextPath()
    31  // enough times to enumerate the options. (In fact, one could argue that the
    32  // raison d'etre for NextPath() is this iterator).
    33  //
    34  // Alternatively, can be seen as the dual of the LinksTo iterator.
    35  
    36  import (
    37  	"context"
    38  	"fmt"
    39  
    40  	"github.com/cayleygraph/cayley/clog"
    41  	"github.com/cayleygraph/cayley/graph"
    42  	"github.com/cayleygraph/quad"
    43  )
    44  
    45  var _ graph.IteratorFuture = &HasA{}
    46  
    47  // A HasA consists of a reference back to the graph.QuadStore that it references,
    48  // a primary subiterator, a direction in which the quads for that subiterator point,
    49  // and a temporary holder for the iterator generated on Contains().
    50  type HasA struct {
    51  	it *hasA
    52  	graph.Iterator
    53  }
    54  
    55  // Construct a new HasA iterator, given the quad subiterator, and the quad
    56  // direction for which it stands.
    57  func NewHasA(qs graph.QuadIndexer, subIt graph.Iterator, d quad.Direction) *HasA {
    58  	it := &HasA{
    59  		it: newHasA(qs, graph.AsShape(subIt), d),
    60  	}
    61  	it.Iterator = graph.NewLegacy(it.it, it)
    62  	return it
    63  }
    64  
    65  func (it *HasA) AsShape() graph.IteratorShape {
    66  	it.Close()
    67  	return it.it
    68  }
    69  
    70  // Direction accessor.
    71  func (it *HasA) Direction() quad.Direction { return it.it.Direction() }
    72  
    73  var _ graph.IteratorShapeCompat = &hasA{}
    74  
    75  // A HasA consists of a reference back to the graph.QuadStore that it references,
    76  // a primary subiterator, a direction in which the quads for that subiterator point,
    77  // and a temporary holder for the iterator generated on Contains().
    78  type hasA struct {
    79  	qs      graph.QuadIndexer
    80  	primary graph.IteratorShape
    81  	dir     quad.Direction
    82  }
    83  
    84  // Construct a new HasA iterator, given the quad subiterator, and the quad
    85  // direction for which it stands.
    86  func newHasA(qs graph.QuadIndexer, subIt graph.IteratorShape, d quad.Direction) *hasA {
    87  	return &hasA{
    88  		qs:      qs,
    89  		primary: subIt,
    90  		dir:     d,
    91  	}
    92  }
    93  
    94  func (it *hasA) Iterate() graph.Scanner {
    95  	return newHasANext(it.qs, it.primary.Iterate(), it.dir)
    96  }
    97  
    98  func (it *hasA) Lookup() graph.Index {
    99  	return newHasAContains(it.qs, it.primary.Lookup(), it.dir)
   100  }
   101  
   102  func (it *hasA) AsLegacy() graph.Iterator {
   103  	it2 := &HasA{it: it}
   104  	it2.Iterator = graph.NewLegacy(it, it2)
   105  	return it2
   106  }
   107  
   108  // Return our sole subiterator.
   109  func (it *hasA) SubIterators() []graph.IteratorShape {
   110  	return []graph.IteratorShape{it.primary}
   111  }
   112  
   113  // Direction accessor.
   114  func (it *hasA) Direction() quad.Direction { return it.dir }
   115  
   116  // Pass the Optimize() call along to the subiterator. If it becomes Null,
   117  // then the HasA becomes Null (there are no quads that have any directions).
   118  func (it *hasA) Optimize(ctx context.Context) (graph.IteratorShape, bool) {
   119  	newPrimary, changed := it.primary.Optimize(ctx)
   120  	if changed {
   121  		it.primary = newPrimary
   122  		if IsNull2(it.primary) {
   123  			return it.primary, true
   124  		}
   125  	}
   126  	return it, false
   127  }
   128  
   129  func (it *hasA) String() string {
   130  	return fmt.Sprintf("HasA(%v)", it.dir)
   131  }
   132  
   133  // GetStats() returns the statistics on the HasA iterator. This is curious. Next
   134  // cost is easy, it's an extra call or so on top of the subiterator Next cost.
   135  // ContainsCost involves going to the graph.QuadStore, iterating out values, and hoping
   136  // one sticks -- potentially expensive, depending on fanout. Size, however, is
   137  // potentially smaller. we know at worst it's the size of the subiterator, but
   138  // if there are many repeated values, it could be much smaller in totality.
   139  func (it *hasA) Stats(ctx context.Context) (graph.IteratorCosts, error) {
   140  	subitStats, err := it.primary.Stats(ctx)
   141  	// TODO(barakmich): These should really come from the quadstore itself
   142  	// and be optimized.
   143  	faninFactor := int64(1)
   144  	fanoutFactor := int64(30)
   145  	nextConstant := int64(2)
   146  	quadConstant := int64(1)
   147  	return graph.IteratorCosts{
   148  		NextCost:     quadConstant + subitStats.NextCost,
   149  		ContainsCost: (fanoutFactor * nextConstant) * subitStats.ContainsCost,
   150  		Size: graph.Size{
   151  			Size:  faninFactor * subitStats.Size.Size,
   152  			Exact: false,
   153  		},
   154  	}, err
   155  }
   156  
   157  // A HasA consists of a reference back to the graph.QuadStore that it references,
   158  // a primary subiterator, a direction in which the quads for that subiterator point,
   159  // and a temporary holder for the iterator generated on Contains().
   160  type hasANext struct {
   161  	qs      graph.QuadIndexer
   162  	primary graph.Scanner
   163  	dir     quad.Direction
   164  	result  graph.Ref
   165  }
   166  
   167  // Construct a new HasA iterator, given the quad subiterator, and the quad
   168  // direction for which it stands.
   169  func newHasANext(qs graph.QuadIndexer, subIt graph.Scanner, d quad.Direction) *hasANext {
   170  	return &hasANext{
   171  		qs:      qs,
   172  		primary: subIt,
   173  		dir:     d,
   174  	}
   175  }
   176  
   177  // Direction accessor.
   178  func (it *hasANext) Direction() quad.Direction { return it.dir }
   179  
   180  // Pass the TagResults down the chain.
   181  func (it *hasANext) TagResults(dst map[string]graph.Ref) {
   182  	it.primary.TagResults(dst)
   183  }
   184  
   185  func (it *hasANext) String() string {
   186  	return fmt.Sprintf("HasANext(%v)", it.dir)
   187  }
   188  
   189  // Get the next result that matches this branch.
   190  func (it *hasANext) NextPath(ctx context.Context) bool {
   191  	return it.primary.NextPath(ctx)
   192  }
   193  
   194  // Next advances the iterator. This is simpler than Contains. We have a
   195  // subiterator we can get a value from, and we can take that resultant quad,
   196  // pull our direction out of it, and return that.
   197  func (it *hasANext) Next(ctx context.Context) bool {
   198  	if !it.primary.Next(ctx) {
   199  		return false
   200  	}
   201  	it.result = it.qs.QuadDirection(it.primary.Result(), it.dir)
   202  	return true
   203  }
   204  
   205  func (it *hasANext) Err() error {
   206  	return it.primary.Err()
   207  }
   208  
   209  func (it *hasANext) Result() graph.Ref {
   210  	return it.result
   211  }
   212  
   213  // Close the subiterator, the result iterator (if any) and the HasA. It closes
   214  // all subiterators it can, but returns the first error it encounters.
   215  func (it *hasANext) Close() error {
   216  	return it.primary.Close()
   217  }
   218  
   219  // A HasA consists of a reference back to the graph.QuadStore that it references,
   220  // a primary subiterator, a direction in which the quads for that subiterator point,
   221  // and a temporary holder for the iterator generated on Contains().
   222  type hasAContains struct {
   223  	qs      graph.QuadIndexer
   224  	primary graph.Index
   225  	dir     quad.Direction
   226  	results graph.Scanner
   227  	result  graph.Ref
   228  	err     error
   229  }
   230  
   231  // Construct a new HasA iterator, given the quad subiterator, and the quad
   232  // direction for which it stands.
   233  func newHasAContains(qs graph.QuadIndexer, subIt graph.Index, d quad.Direction) graph.Index {
   234  	return &hasAContains{
   235  		qs:      qs,
   236  		primary: subIt,
   237  		dir:     d,
   238  	}
   239  }
   240  
   241  // Direction accessor.
   242  func (it *hasAContains) Direction() quad.Direction { return it.dir }
   243  
   244  // Pass the TagResults down the chain.
   245  func (it *hasAContains) TagResults(dst map[string]graph.Ref) {
   246  	it.primary.TagResults(dst)
   247  }
   248  
   249  func (it *hasAContains) String() string {
   250  	return fmt.Sprintf("HasAContains(%v)", it.dir)
   251  }
   252  
   253  // Check a value against our internal iterator. In order to do this, we must first open a new
   254  // iterator of "quads that have `val` in our direction", given to us by the quad store,
   255  // and then Next() values out of that iterator and Contains() them against our subiterator.
   256  func (it *hasAContains) Contains(ctx context.Context, val graph.Ref) bool {
   257  	if clog.V(4) {
   258  		clog.Infof("Id is %v", val)
   259  	}
   260  	// TODO(barakmich): Optimize this
   261  	if it.results != nil {
   262  		it.results.Close()
   263  	}
   264  	it.results = graph.AsShape(it.qs.QuadIterator(it.dir, val)).Iterate()
   265  	ok := it.nextContains(ctx)
   266  	if it.err != nil {
   267  		return false
   268  	}
   269  	return ok
   270  }
   271  
   272  // nextContains() is shared code between Contains() and GetNextResult() -- calls next on the
   273  // result iterator (a quad iterator based on the last checked value) and returns true if
   274  // another match is made.
   275  func (it *hasAContains) nextContains(ctx context.Context) bool {
   276  	if it.results == nil {
   277  		return false
   278  	}
   279  	for it.results.Next(ctx) {
   280  		link := it.results.Result()
   281  		if clog.V(4) {
   282  			clog.Infof("Quad is %v", it.qs.Quad(link))
   283  		}
   284  		if it.primary.Contains(ctx, link) {
   285  			it.result = it.qs.QuadDirection(link, it.dir)
   286  			return true
   287  		}
   288  	}
   289  	it.err = it.results.Err()
   290  	return false
   291  }
   292  
   293  // Get the next result that matches this branch.
   294  func (it *hasAContains) NextPath(ctx context.Context) bool {
   295  	// Order here is important. If the subiterator has a NextPath, then we
   296  	// need do nothing -- there is a next result, and we shouldn't move forward.
   297  	// However, we then need to get the next result from our last Contains().
   298  	//
   299  	// The upshot is, the end of NextPath() bubbles up from the bottom of the
   300  	// iterator tree up, and we need to respect that.
   301  	if clog.V(4) {
   302  		clog.Infof("HASA %p NextPath", it)
   303  	}
   304  	if it.primary.NextPath(ctx) {
   305  		return true
   306  	}
   307  	it.err = it.primary.Err()
   308  	if it.err != nil {
   309  		return false
   310  	}
   311  
   312  	result := it.nextContains(ctx) // Sets it.err if there's an error
   313  	if it.err != nil {
   314  		return false
   315  	}
   316  	if clog.V(4) {
   317  		clog.Infof("HASA %p NextPath Returns %v", it, result)
   318  	}
   319  	return result
   320  }
   321  
   322  func (it *hasAContains) Err() error {
   323  	return it.err
   324  }
   325  
   326  func (it *hasAContains) Result() graph.Ref {
   327  	return it.result
   328  }
   329  
   330  // Close the subiterator, the result iterator (if any) and the HasA. It closes
   331  // all subiterators it can, but returns the first error it encounters.
   332  func (it *hasAContains) Close() error {
   333  	err := it.primary.Close()
   334  	if it.results != nil {
   335  		if err2 := it.results.Close(); err2 != nil && err == nil {
   336  			err = err2
   337  		}
   338  	}
   339  	return err
   340  }