github.com/cayleygraph/cayley@v0.7.7/graph/iterator/or.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 the or and short-circuiting or iterator. Or is the union operator for it's subiterators.
    18  // Short-circuiting-or is a little different. It will return values from the first graph.iterator that returns
    19  // values at all, and then stops.
    20  //
    21  // Never reorders the iterators from the order they arrive. It is either the union or the first one.
    22  // May return the same value twice -- once for each branch.
    23  
    24  import (
    25  	"context"
    26  
    27  	"github.com/cayleygraph/cayley/graph"
    28  )
    29  
    30  var _ graph.IteratorFuture = &Or{}
    31  
    32  type Or struct {
    33  	it *or
    34  	graph.Iterator
    35  }
    36  
    37  func NewOr(sub ...graph.Iterator) *Or {
    38  	in := make([]graph.IteratorShape, 0, len(sub))
    39  	for _, s := range sub {
    40  		in = append(in, graph.AsShape(s))
    41  	}
    42  	it := &Or{
    43  		it: newOr(in...),
    44  	}
    45  	it.Iterator = graph.NewLegacy(it.it, it)
    46  	return it
    47  }
    48  
    49  func NewShortCircuitOr(sub ...graph.Iterator) *Or {
    50  	in := make([]graph.IteratorShape, 0, len(sub))
    51  	for _, s := range sub {
    52  		in = append(in, graph.AsShape(s))
    53  	}
    54  	it := &Or{
    55  		it: newShortCircuitOr(in...),
    56  	}
    57  	it.Iterator = graph.NewLegacy(it.it, it)
    58  	return it
    59  }
    60  
    61  func (it *Or) AsShape() graph.IteratorShape {
    62  	it.Close()
    63  	return it.it
    64  }
    65  
    66  // Add a subiterator to this Or graph.iterator. Order matters.
    67  func (it *Or) AddSubIterator(sub graph.Iterator) {
    68  	it.it.AddSubIterator(graph.AsShape(sub))
    69  }
    70  
    71  var _ graph.IteratorShapeCompat = &or{}
    72  
    73  type or struct {
    74  	isShortCircuiting bool
    75  	sub               []graph.IteratorShape
    76  	curInd            int
    77  	result            graph.Ref
    78  	err               error
    79  }
    80  
    81  func newOr(sub ...graph.IteratorShape) *or {
    82  	it := &or{
    83  		sub:    make([]graph.IteratorShape, 0, 20),
    84  		curInd: -1,
    85  	}
    86  	for _, s := range sub {
    87  		it.AddSubIterator(s)
    88  	}
    89  	return it
    90  }
    91  
    92  func newShortCircuitOr(sub ...graph.IteratorShape) *or {
    93  	it := &or{
    94  		sub:               make([]graph.IteratorShape, 0, 20),
    95  		isShortCircuiting: true,
    96  		curInd:            -1,
    97  	}
    98  	for _, s := range sub {
    99  		it.AddSubIterator(s)
   100  	}
   101  	return it
   102  }
   103  
   104  func (it *or) Iterate() graph.Scanner {
   105  	sub := make([]graph.Scanner, 0, len(it.sub))
   106  	for _, s := range it.sub {
   107  		sub = append(sub, s.Iterate())
   108  	}
   109  	return newOrNext(sub, it.isShortCircuiting)
   110  }
   111  
   112  func (it *or) Lookup() graph.Index {
   113  	sub := make([]graph.Index, 0, len(it.sub))
   114  	for _, s := range it.sub {
   115  		sub = append(sub, s.Lookup())
   116  	}
   117  	return newOrContains(sub, it.isShortCircuiting)
   118  }
   119  
   120  func (it *or) AsLegacy() graph.Iterator {
   121  	it2 := &Or{it: it}
   122  	it2.Iterator = graph.NewLegacy(it, it2)
   123  	return it2
   124  }
   125  
   126  // Returns a list.List of the subiterators, in order. The returned slice must not be modified.
   127  func (it *or) SubIterators() []graph.IteratorShape {
   128  	return it.sub
   129  }
   130  
   131  func (it *or) String() string {
   132  	return "Or"
   133  }
   134  
   135  // Add a subiterator to this Or graph.iterator. Order matters.
   136  func (it *or) AddSubIterator(sub graph.IteratorShape) {
   137  	it.sub = append(it.sub, sub)
   138  }
   139  
   140  func (it *or) Optimize(ctx context.Context) (graph.IteratorShape, bool) {
   141  	old := it.SubIterators()
   142  	optIts := optimizeSubIterators2(ctx, old)
   143  	newOr := newOr()
   144  	newOr.isShortCircuiting = it.isShortCircuiting
   145  
   146  	// Add the subiterators in order.
   147  	for _, o := range optIts {
   148  		newOr.AddSubIterator(o)
   149  	}
   150  	return newOr, true
   151  }
   152  
   153  // Returns the approximate size of the Or graph.iterator. Because we're dealing
   154  // with a union, we know that the largest we can be is the sum of all the iterators,
   155  // or in the case of short-circuiting, the longest.
   156  func (it *or) Stats(ctx context.Context) (graph.IteratorCosts, error) {
   157  	ContainsCost := int64(0)
   158  	NextCost := int64(0)
   159  	Size := graph.Size{
   160  		Size:  0,
   161  		Exact: true,
   162  	}
   163  	var last error
   164  	for _, sub := range it.sub {
   165  		stats, err := sub.Stats(ctx)
   166  		if err != nil {
   167  			last = err
   168  		}
   169  		NextCost += stats.NextCost
   170  		ContainsCost += stats.ContainsCost
   171  		if it.isShortCircuiting {
   172  			if Size.Size < stats.Size.Size {
   173  				Size = stats.Size
   174  			}
   175  		} else {
   176  			Size.Size += stats.Size.Size
   177  			Size.Exact = Size.Exact && stats.Size.Exact
   178  		}
   179  	}
   180  	return graph.IteratorCosts{
   181  		ContainsCost: ContainsCost,
   182  		NextCost:     NextCost,
   183  		Size:         Size,
   184  	}, last
   185  }
   186  
   187  type orNext struct {
   188  	shortCircuit bool
   189  	sub          []graph.Scanner
   190  	curInd       int
   191  	result       graph.Ref
   192  	err          error
   193  }
   194  
   195  func newOrNext(sub []graph.Scanner, shortCircuit bool) *orNext {
   196  	return &orNext{
   197  		sub:          sub,
   198  		curInd:       -1,
   199  		shortCircuit: shortCircuit,
   200  	}
   201  }
   202  
   203  // Overrides BaseIterator TagResults, as it needs to add it's own results and
   204  // recurse down it's subiterators.
   205  func (it *orNext) TagResults(dst map[string]graph.Ref) {
   206  	it.sub[it.curInd].TagResults(dst)
   207  }
   208  
   209  func (it *orNext) String() string {
   210  	return "OrNext"
   211  }
   212  
   213  // Next advances the Or graph.iterator. Because the Or is the union of its
   214  // subiterators, it must produce from all subiterators -- unless it it
   215  // shortcircuiting, in which case, it is the first one that returns anything.
   216  func (it *orNext) Next(ctx context.Context) bool {
   217  	if it.curInd >= len(it.sub) {
   218  		return false
   219  	}
   220  	var first bool
   221  	for {
   222  		if it.curInd == -1 {
   223  			it.curInd = 0
   224  			first = true
   225  		}
   226  		curIt := it.sub[it.curInd]
   227  
   228  		if curIt.Next(ctx) {
   229  			it.result = curIt.Result()
   230  			return true
   231  		}
   232  
   233  		it.err = curIt.Err()
   234  		if it.err != nil {
   235  			return false
   236  		}
   237  
   238  		if it.shortCircuit && !first {
   239  			break
   240  		}
   241  		it.curInd++
   242  		if it.curInd >= len(it.sub) {
   243  			break
   244  		}
   245  	}
   246  
   247  	return false
   248  }
   249  
   250  func (it *orNext) Err() error {
   251  	return it.err
   252  }
   253  
   254  func (it *orNext) Result() graph.Ref {
   255  	return it.result
   256  }
   257  
   258  // An Or has no NextPath of its own -- that is, there are no other values
   259  // which satisfy our previous result that are not the result itself. Our
   260  // subiterators might, however, so just pass the call recursively. In the case of
   261  // shortcircuiting, only allow new results from the currently checked graph.iterator
   262  func (it *orNext) NextPath(ctx context.Context) bool {
   263  	if it.curInd != -1 {
   264  		currIt := it.sub[it.curInd]
   265  		ok := currIt.NextPath(ctx)
   266  		if !ok {
   267  			it.err = currIt.Err()
   268  		}
   269  		return ok
   270  	}
   271  	return false
   272  }
   273  
   274  // Close this graph.iterator, and, by extension, close the subiterators.
   275  // Close should be idempotent, and it follows that if it's subiterators
   276  // follow this contract, the Or follows the contract.  It closes all
   277  // subiterators it can, but returns the first error it encounters.
   278  func (it *orNext) Close() error {
   279  	var err error
   280  	for _, sub := range it.sub {
   281  		_err := sub.Close()
   282  		if _err != nil && err == nil {
   283  			err = _err
   284  		}
   285  	}
   286  	return err
   287  }
   288  
   289  var _ graph.Iterator = &Or{}
   290  
   291  type orContains struct {
   292  	shortCircuit bool
   293  	sub          []graph.Index
   294  	curInd       int
   295  	result       graph.Ref
   296  	err          error
   297  }
   298  
   299  func newOrContains(sub []graph.Index, shortCircuit bool) *orContains {
   300  	return &orContains{
   301  		sub:          sub,
   302  		curInd:       -1,
   303  		shortCircuit: shortCircuit,
   304  	}
   305  }
   306  
   307  // Overrides BaseIterator TagResults, as it needs to add it's own results and
   308  // recurse down it's subiterators.
   309  func (it *orContains) TagResults(dst map[string]graph.Ref) {
   310  	it.sub[it.curInd].TagResults(dst)
   311  }
   312  
   313  func (it *orContains) String() string {
   314  	return "OrContains"
   315  }
   316  
   317  func (it *orContains) Err() error {
   318  	return it.err
   319  }
   320  
   321  func (it *orContains) Result() graph.Ref {
   322  	return it.result
   323  }
   324  
   325  // Checks a value against the iterators, in order.
   326  func (it *orContains) subItsContain(ctx context.Context, val graph.Ref) (bool, error) {
   327  	subIsGood := false
   328  	for i, sub := range it.sub {
   329  		subIsGood = sub.Contains(ctx, val)
   330  		if subIsGood {
   331  			it.curInd = i
   332  			break
   333  		}
   334  
   335  		err := sub.Err()
   336  		if err != nil {
   337  			return false, err
   338  		}
   339  	}
   340  	return subIsGood, nil
   341  }
   342  
   343  // Check a value against the entire graph.iterator, in order.
   344  func (it *orContains) Contains(ctx context.Context, val graph.Ref) bool {
   345  	anyGood, err := it.subItsContain(ctx, val)
   346  	if err != nil {
   347  		it.err = err
   348  		return false
   349  	} else if !anyGood {
   350  		return false
   351  	}
   352  	it.result = val
   353  	return true
   354  }
   355  
   356  // An Or has no NextPath of its own -- that is, there are no other values
   357  // which satisfy our previous result that are not the result itself. Our
   358  // subiterators might, however, so just pass the call recursively. In the case of
   359  // shortcircuiting, only allow new results from the currently checked graph.iterator
   360  func (it *orContains) NextPath(ctx context.Context) bool {
   361  	if it.curInd != -1 {
   362  		currIt := it.sub[it.curInd]
   363  		ok := currIt.NextPath(ctx)
   364  		if !ok {
   365  			it.err = currIt.Err()
   366  		}
   367  		return ok
   368  	}
   369  	// TODO(dennwc): this should probably list matches from other sub-iterators
   370  	return false
   371  }
   372  
   373  // Close this graph.iterator, and, by extension, close the subiterators.
   374  // Close should be idempotent, and it follows that if it's subiterators
   375  // follow this contract, the Or follows the contract.  It closes all
   376  // subiterators it can, but returns the first error it encounters.
   377  func (it *orContains) Close() error {
   378  	var err error
   379  	for _, sub := range it.sub {
   380  		_err := sub.Close()
   381  		if _err != nil && err == nil {
   382  			err = _err
   383  		}
   384  	}
   385  	return err
   386  }