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

     1  // Defines the And iterator, one of the base iterators. And requires no
     2  // knowledge of the constituent QuadStore; its sole purpose is to act as an
     3  // intersection operator across the subiterators it is given. If one iterator
     4  // contains [1,3,5] and another [2,3,4] -- then And is an iterator that
     5  // 'contains' [3]
     6  //
     7  // It accomplishes this in one of two ways. If it is a Next()ed iterator (that
     8  // is, it is a top level iterator, or on the "Next() path", then it will Next()
     9  // it's primary iterator (helpfully, and.primary_it) and Contains() the resultant
    10  // value against it's other iterators. If it matches all of them, then it
    11  // returns that value. Otherwise, it repeats the process.
    12  //
    13  // If it's on a Contains() path, it merely Contains()s every iterator, and returns the
    14  // logical AND of each result.
    15  
    16  package iterator
    17  
    18  import (
    19  	"context"
    20  
    21  	"github.com/cayleygraph/cayley/graph"
    22  )
    23  
    24  var _ graph.IteratorFuture = &And{}
    25  
    26  // The And iterator. Consists of a number of subiterators, the primary of which will
    27  // be Next()ed if next is called.
    28  type And struct {
    29  	it *and
    30  	graph.Iterator
    31  }
    32  
    33  // NewAnd creates an And iterator. `qs` is only required when needing a handle
    34  // for QuadStore-specific optimizations, otherwise nil is acceptable.
    35  func NewAnd(sub ...graph.Iterator) *And {
    36  	it := &And{
    37  		it: newAnd(),
    38  	}
    39  	for _, s := range sub {
    40  		it.it.AddSubIterator(graph.AsShape(s))
    41  	}
    42  	it.Iterator = graph.NewLegacy(it.it, it)
    43  	return it
    44  }
    45  
    46  func (it *And) AsShape() graph.IteratorShape {
    47  	it.Close()
    48  	return it.it
    49  }
    50  
    51  // Add a subiterator to this And iterator.
    52  //
    53  // The first iterator that is added becomes the primary iterator. This is
    54  // important. Calling Optimize() is the way to change the order based on
    55  // subiterator statistics. Without Optimize(), the order added is the order
    56  // used.
    57  func (it *And) AddSubIterator(sub graph.Iterator) {
    58  	it.it.AddSubIterator(graph.AsShape(sub))
    59  }
    60  
    61  // AddOptionalIterator adds an iterator that will only be Contain'ed and will not affect iteration results.
    62  // Only tags will be propagated from this iterator.
    63  func (it *And) AddOptionalIterator(sub graph.Iterator) *And {
    64  	it.it.AddOptionalIterator(graph.AsShape(sub))
    65  	return it
    66  }
    67  
    68  var _ graph.IteratorShapeCompat = &and{}
    69  
    70  // The And iterator. Consists of a number of subiterators, the primary of which will
    71  // be Next()ed if next is called.
    72  type and struct {
    73  	sub       []graph.IteratorShape
    74  	checkList []graph.IteratorShape // special order for Contains
    75  	opt       []graph.IteratorShape
    76  }
    77  
    78  // NewAnd creates an And iterator. `qs` is only required when needing a handle
    79  // for QuadStore-specific optimizations, otherwise nil is acceptable.
    80  func newAnd(sub ...graph.IteratorShape) *and {
    81  	it := &and{
    82  		sub: make([]graph.IteratorShape, 0, 20),
    83  	}
    84  	for _, s := range sub {
    85  		it.AddSubIterator(s)
    86  	}
    87  	return it
    88  }
    89  
    90  func (it *and) Iterate() graph.Scanner {
    91  	if len(it.sub) == 0 {
    92  		return newNull().Iterate()
    93  	}
    94  	sub := make([]graph.Index, 0, len(it.sub)-1)
    95  	for _, s := range it.sub[1:] {
    96  		sub = append(sub, s.Lookup())
    97  	}
    98  	opt := make([]graph.Index, 0, len(it.opt))
    99  	for _, s := range it.opt {
   100  		opt = append(opt, s.Lookup())
   101  	}
   102  	return newAndNext(it.sub[0].Iterate(), newAndContains(sub, opt))
   103  }
   104  
   105  func (it *and) Lookup() graph.Index {
   106  	if len(it.sub) == 0 {
   107  		return newNull().Lookup()
   108  	}
   109  	sub := make([]graph.Index, 0, len(it.sub))
   110  	check := it.checkList
   111  	if check == nil {
   112  		check = it.sub
   113  	}
   114  	for _, s := range check {
   115  		sub = append(sub, s.Lookup())
   116  	}
   117  	opt := make([]graph.Index, 0, len(it.opt))
   118  	for _, s := range it.opt {
   119  		opt = append(opt, s.Lookup())
   120  	}
   121  	return newAndContains(sub, opt)
   122  }
   123  
   124  func (it *and) AsLegacy() graph.Iterator {
   125  	it2 := &And{it: it}
   126  	it2.Iterator = graph.NewLegacy(it, it2)
   127  	return it2
   128  }
   129  
   130  // Returns a slice of the subiterators, in order (primary iterator first).
   131  func (it *and) SubIterators() []graph.IteratorShape {
   132  	iters := make([]graph.IteratorShape, 0, len(it.sub)+len(it.opt))
   133  	iters = append(iters, it.sub...)
   134  	iters = append(iters, it.opt...)
   135  	return iters
   136  }
   137  
   138  func (it *and) String() string {
   139  	return "And"
   140  }
   141  
   142  // Add a subiterator to this And iterator.
   143  //
   144  // The first iterator that is added becomes the primary iterator. This is
   145  // important. Calling Optimize() is the way to change the order based on
   146  // subiterator statistics. Without Optimize(), the order added is the order
   147  // used.
   148  func (it *and) AddSubIterator(sub graph.IteratorShape) {
   149  	if sub == nil {
   150  		panic("nil iterator")
   151  	}
   152  	it.sub = append(it.sub, sub)
   153  }
   154  
   155  // AddOptionalIterator adds an iterator that will only be Contain'ed and will not affect iteration results.
   156  // Only tags will be propagated from this iterator.
   157  func (it *and) AddOptionalIterator(sub graph.IteratorShape) *and {
   158  	it.opt = append(it.opt, sub)
   159  	return it
   160  }
   161  
   162  // The And iterator. Consists of a number of subiterators, the primary of which will
   163  // be Next()ed if next is called.
   164  type andNext struct {
   165  	primary   graph.Scanner
   166  	secondary graph.Index
   167  	result    graph.Ref
   168  }
   169  
   170  // NewAnd creates an And iterator. `qs` is only required when needing a handle
   171  // for QuadStore-specific optimizations, otherwise nil is acceptable.
   172  func newAndNext(pri graph.Scanner, sec graph.Index) graph.Scanner {
   173  	return &andNext{
   174  		primary:   pri,
   175  		secondary: sec,
   176  	}
   177  }
   178  
   179  // An extended TagResults, as it needs to add it's own results and
   180  // recurse down it's subiterators.
   181  func (it *andNext) TagResults(dst map[string]graph.Ref) {
   182  	it.primary.TagResults(dst)
   183  	it.secondary.TagResults(dst)
   184  }
   185  
   186  func (it *andNext) String() string {
   187  	return "AndNext"
   188  }
   189  
   190  // Returns advances the And iterator. Because the And is the intersection of its
   191  // subiterators, it must choose one subiterator to produce a candidate, and check
   192  // this value against the subiterators. A productive choice of primary iterator
   193  // is therefore very important.
   194  func (it *andNext) Next(ctx context.Context) bool {
   195  	for it.primary.Next(ctx) {
   196  		cur := it.primary.Result()
   197  		if it.secondary.Contains(ctx, cur) {
   198  			it.result = cur
   199  			return true
   200  		}
   201  	}
   202  	return false
   203  }
   204  
   205  func (it *andNext) Err() error {
   206  	if err := it.primary.Err(); err != nil {
   207  		return err
   208  	}
   209  	if err := it.secondary.Err(); err != nil {
   210  		return err
   211  	}
   212  	return nil
   213  }
   214  
   215  func (it *andNext) Result() graph.Ref {
   216  	return it.result
   217  }
   218  
   219  // An And has no NextPath of its own -- that is, there are no other values
   220  // which satisfy our previous result that are not the result itself. Our
   221  // subiterators might, however, so just pass the call recursively.
   222  func (it *andNext) NextPath(ctx context.Context) bool {
   223  	if it.primary.NextPath(ctx) {
   224  		return true
   225  	} else if err := it.primary.Err(); err != nil {
   226  		return false
   227  	}
   228  	if it.secondary.NextPath(ctx) {
   229  		return true
   230  	} else if err := it.secondary.Err(); err != nil {
   231  		return false
   232  	}
   233  	return false
   234  }
   235  
   236  // Close this iterator, and, by extension, close the subiterators.
   237  // Close should be idempotent, and it follows that if it's subiterators
   238  // follow this contract, the And follows the contract.  It closes all
   239  // subiterators it can, but returns the first error it encounters.
   240  func (it *andNext) Close() error {
   241  	err := it.primary.Close()
   242  	if err2 := it.secondary.Close(); err2 != nil && err == nil {
   243  		err = err2
   244  	}
   245  	return err
   246  }
   247  
   248  // The And iterator. Consists of a number of subiterators, the primary of which will
   249  // be Next()ed if next is called.
   250  type andContains struct {
   251  	base     graph.IteratorShape
   252  	sub      []graph.Index
   253  	opt      []graph.Index
   254  	optCheck []bool
   255  
   256  	result graph.Ref
   257  	err    error
   258  }
   259  
   260  // NewAnd creates an And iterator. `qs` is only required when needing a handle
   261  // for QuadStore-specific optimizations, otherwise nil is acceptable.
   262  func newAndContains(sub, opt []graph.Index) graph.Index {
   263  	return &andContains{
   264  		sub: sub,
   265  		opt: opt, optCheck: make([]bool, len(opt)),
   266  	}
   267  }
   268  
   269  // An extended TagResults, as it needs to add it's own results and
   270  // recurse down it's subiterators.
   271  func (it *andContains) TagResults(dst map[string]graph.Ref) {
   272  	for _, sub := range it.sub {
   273  		sub.TagResults(dst)
   274  	}
   275  	for i, sub := range it.opt {
   276  		if !it.optCheck[i] {
   277  			continue
   278  		}
   279  		sub.TagResults(dst)
   280  	}
   281  }
   282  
   283  func (it *andContains) String() string {
   284  	return "AndContains"
   285  }
   286  
   287  func (it *andContains) Err() error {
   288  	if err := it.err; err != nil {
   289  		return err
   290  	}
   291  	for _, si := range it.sub {
   292  		if err := si.Err(); err != nil {
   293  			return err
   294  		}
   295  	}
   296  	for _, si := range it.opt {
   297  		if err := si.Err(); err != nil {
   298  			return err
   299  		}
   300  	}
   301  	return nil
   302  }
   303  
   304  func (it *andContains) Result() graph.Ref {
   305  	return it.result
   306  }
   307  
   308  // Check a value against the entire iterator, in order.
   309  func (it *andContains) Contains(ctx context.Context, val graph.Ref) bool {
   310  	prev := it.result
   311  	for i, sub := range it.sub {
   312  		if !sub.Contains(ctx, val) {
   313  			if err := sub.Err(); err != nil {
   314  				it.err = err
   315  				return false
   316  			}
   317  			// One of the iterators has determined that this value doesn't
   318  			// match. However, the iterators that came before in the list
   319  			// may have returned "ok" to Contains().  We need to set all
   320  			// the tags back to what the previous result was -- effectively
   321  			// seeking back exactly one -- so we check all the prior iterators
   322  			// with the (already verified) result and throw away the result,
   323  			// which will be 'true'
   324  			if prev != nil {
   325  				for j := 0; j < i; j++ {
   326  					it.sub[j].Contains(ctx, prev)
   327  					if err := it.sub[j].Err(); err != nil {
   328  						it.err = err
   329  						return false
   330  					}
   331  				}
   332  			}
   333  			return false
   334  		}
   335  	}
   336  	it.result = val
   337  	for i, sub := range it.opt {
   338  		// remember if we will need to call TagResults on it, nothing more
   339  		it.optCheck[i] = sub.Contains(ctx, val)
   340  	}
   341  	return true
   342  }
   343  
   344  // An And has no NextPath of its own -- that is, there are no other values
   345  // which satisfy our previous result that are not the result itself. Our
   346  // subiterators might, however, so just pass the call recursively.
   347  func (it *andContains) NextPath(ctx context.Context) bool {
   348  	for _, sub := range it.sub {
   349  		if sub.NextPath(ctx) {
   350  			return true
   351  		} else if err := sub.Err(); err != nil {
   352  			it.err = err
   353  			return false
   354  		}
   355  	}
   356  	for i, sub := range it.opt {
   357  		if !it.optCheck[i] {
   358  			continue
   359  		}
   360  		if sub.NextPath(ctx) {
   361  			return true
   362  		} else if err := sub.Err(); err != nil {
   363  			it.err = err
   364  			return false
   365  		}
   366  	}
   367  	return false
   368  }
   369  
   370  // Close this iterator, and, by extension, close the subiterators.
   371  // Close should be idempotent, and it follows that if it's subiterators
   372  // follow this contract, the And follows the contract.  It closes all
   373  // subiterators it can, but returns the first error it encounters.
   374  func (it *andContains) Close() error {
   375  	var err error
   376  	for _, sub := range it.sub {
   377  		if err2 := sub.Close(); err2 != nil && err == nil {
   378  			err = err2
   379  		}
   380  	}
   381  	for _, sub := range it.opt {
   382  		if err2 := sub.Close(); err2 != nil && err == nil {
   383  			err = err2
   384  		}
   385  	}
   386  	return err
   387  }