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

     1  package graph
     2  
     3  import "context"
     4  
     5  // Scanner is an iterator that lists all results sequentially, but not necessarily in a sorted order.
     6  type Scanner interface {
     7  	IteratorBase
     8  
     9  	// Next advances the iterator to the next value, which will then be available through
    10  	// the Result method. It returns false if no further advancement is possible, or if an
    11  	// error was encountered during iteration.  Err should be consulted to distinguish
    12  	// between the two cases.
    13  	Next(ctx context.Context) bool
    14  }
    15  
    16  // Index is an index lookup iterator. It allows to check if an index contains a specific value.
    17  type Index interface {
    18  	IteratorBase
    19  
    20  	// Contains returns whether the value is within the set held by the iterator.
    21  	//
    22  	// It will set Result to the matching subtree. TagResults can be used to collect values from tree branches.
    23  	Contains(ctx context.Context, v Ref) bool
    24  }
    25  
    26  // Tagger is an interface for iterators that can tag values. Tags are returned as a part of TagResults call.
    27  type TaggerShape interface {
    28  	IteratorShape
    29  	TaggerBase
    30  	CopyFromTagger(st TaggerBase)
    31  }
    32  
    33  type IteratorCosts struct {
    34  	ContainsCost int64
    35  	NextCost     int64
    36  	Size         Size
    37  }
    38  
    39  // Shape is an iterator shape, similar to a query plan. But the plan is not specific in this
    40  // case - it is used to reorder query branches, and the decide what branches will be scanned
    41  // and what branches will lookup values (hopefully from the index, but not necessarily).
    42  type IteratorShape interface {
    43  	// TODO(dennwc): merge with shape.Shape
    44  
    45  	// String returns a short textual representation of an iterator.
    46  	String() string
    47  
    48  	// Iterate starts this iterator in scanning mode. Resulting iterator will list all
    49  	// results sequentially, but not necessary in the sorted order. Caller must close
    50  	// the iterator.
    51  	Iterate() Scanner
    52  
    53  	// Lookup starts this iterator in an index lookup mode. Depending on the iterator type,
    54  	// this may still involve database scans. Resulting iterator allows to check an index
    55  	// contains a specified value. Caller must close the iterator.
    56  	Lookup() Index
    57  
    58  	// These methods relate to choosing the right iterator, or optimizing an
    59  	// iterator tree
    60  	//
    61  	// Stats() returns the relative costs of calling the iteration methods for
    62  	// this iterator, as well as the size. Roughly, it will take NextCost * Size
    63  	// "cost units" to get everything out of the iterator. This is a wibbly-wobbly
    64  	// thing, and not exact, but a useful heuristic.
    65  	Stats(ctx context.Context) (IteratorCosts, error)
    66  
    67  	// Optimizes an iterator. Can replace the iterator, or merely move things
    68  	// around internally. if it chooses to replace it with a better iterator,
    69  	// returns (the new iterator, true), if not, it returns (self, false).
    70  	Optimize(ctx context.Context) (IteratorShape, bool)
    71  
    72  	// Return a slice of the subiterators for this iterator.
    73  	SubIterators() []IteratorShape
    74  }
    75  
    76  // IteratorShapeCompat is an optional interface for iterator Shape that support direct conversion
    77  // to a legacy Iterator. This interface should be avoided an will be deprecated in the future.
    78  type IteratorShapeCompat interface {
    79  	IteratorShape
    80  	AsLegacy() Iterator
    81  }
    82  
    83  // AsShape converts a legacy Iterator to an iterator Shape.
    84  func AsShape(it Iterator) IteratorShape {
    85  	if it == nil {
    86  		panic("nil iterator")
    87  	}
    88  	if it2, ok := it.(IteratorFuture); ok {
    89  		return it2.AsShape()
    90  	}
    91  	return &legacyShape{it}
    92  }
    93  
    94  var _ IteratorShapeCompat = &legacyShape{}
    95  
    96  type legacyShape struct {
    97  	Iterator
    98  }
    99  
   100  func (it *legacyShape) Optimize(ctx context.Context) (IteratorShape, bool) {
   101  	nit, ok := it.Iterator.Optimize()
   102  	if !ok {
   103  		return it, false
   104  	}
   105  	return AsShape(nit), true
   106  }
   107  
   108  func (it *legacyShape) SubIterators() []IteratorShape {
   109  	its := it.Iterator.SubIterators()
   110  	out := make([]IteratorShape, 0, len(its))
   111  	for _, s := range its {
   112  		out = append(out, AsShape(s))
   113  	}
   114  	return out
   115  }
   116  
   117  func (it *legacyShape) Stats(ctx context.Context) (IteratorCosts, error) {
   118  	st := it.Iterator.Stats()
   119  	return IteratorCosts{
   120  		NextCost:     st.NextCost,
   121  		ContainsCost: st.ContainsCost,
   122  		Size: Size{
   123  			Size:  st.Size,
   124  			Exact: st.ExactSize,
   125  		},
   126  	}, it.Err()
   127  }
   128  
   129  func (it *legacyShape) Iterate() Scanner {
   130  	it.Reset()
   131  	return it
   132  }
   133  func (it *legacyShape) Lookup() Index {
   134  	it.Reset()
   135  	return it
   136  }
   137  func (it *legacyShape) Close() error {
   138  	// FIXME(dennwc): this is incorrect, but we must do this to prevent closing iterators after
   139  	//                multiple calls to Iterate and/or Lookup
   140  	return nil
   141  }
   142  func (it *legacyShape) AsLegacy() Iterator {
   143  	return it.Iterator
   144  }
   145  
   146  // NewLegacy creates a new legacy Iterator from an iterator Shape.
   147  // This method will always create a new iterator, while AsLegacy will try to unwrap it first.
   148  func NewLegacy(s IteratorShape, self Iterator) Iterator {
   149  	if s == nil {
   150  		panic("nil iterator")
   151  	}
   152  	return &legacyIter{s: s, self: self}
   153  }
   154  
   155  // AsLegacy convert an iterator Shape to a legacy Iterator interface.
   156  func AsLegacy(s IteratorShape) Iterator {
   157  	if it2, ok := s.(IteratorShapeCompat); ok {
   158  		return it2.AsLegacy()
   159  	}
   160  	return NewLegacy(s, nil)
   161  }
   162  
   163  var _ IteratorFuture = &legacyIter{}
   164  
   165  type legacyIter struct {
   166  	s    IteratorShape
   167  	self Iterator
   168  	scan Scanner
   169  	cont Index
   170  }
   171  
   172  func (it *legacyIter) String() string {
   173  	return it.s.String()
   174  }
   175  
   176  func (it *legacyIter) AsShape() IteratorShape {
   177  	it.Close()
   178  	return it.s
   179  }
   180  
   181  func (it *legacyIter) TagResults(m map[string]Ref) {
   182  	if it.cont != nil && it.scan != nil {
   183  		panic("both iterators are set")
   184  	}
   185  	if it.scan != nil {
   186  		it.scan.TagResults(m)
   187  	} else if it.cont != nil {
   188  		it.cont.TagResults(m)
   189  	}
   190  }
   191  
   192  func (it *legacyIter) Result() Ref {
   193  	if it.cont != nil && it.scan != nil {
   194  		panic("both iterators are set")
   195  	}
   196  	if it.scan != nil {
   197  		return it.scan.Result()
   198  	}
   199  	if it.cont != nil {
   200  		return it.cont.Result()
   201  	}
   202  	return nil
   203  }
   204  
   205  func (it *legacyIter) Next(ctx context.Context) bool {
   206  	if it.scan == nil {
   207  		if it.cont != nil {
   208  			panic("attempt to set a scan iterator on contains")
   209  		}
   210  		it.scan = it.s.Iterate()
   211  	}
   212  	return it.scan.Next(ctx)
   213  }
   214  
   215  func (it *legacyIter) NextPath(ctx context.Context) bool {
   216  	if it.cont != nil && it.scan != nil {
   217  		panic("both iterators are set")
   218  	}
   219  	if it.scan != nil {
   220  		return it.scan.NextPath(ctx)
   221  	}
   222  	if it.cont != nil {
   223  		return it.cont.NextPath(ctx)
   224  	}
   225  	panic("calling NextPath before Next or Contains")
   226  }
   227  
   228  func (it *legacyIter) Contains(ctx context.Context, v Ref) bool {
   229  	if it.cont == nil {
   230  		// reset iterator by default
   231  		if it.scan != nil {
   232  			it.scan.Close()
   233  			it.scan = nil
   234  		}
   235  		it.cont = it.s.Lookup()
   236  	}
   237  	return it.cont.Contains(ctx, v)
   238  }
   239  
   240  func (it *legacyIter) Err() error {
   241  	if it.scan != nil {
   242  		if err := it.scan.Err(); err != nil {
   243  			return err
   244  		}
   245  	}
   246  	if it.cont != nil {
   247  		if err := it.cont.Err(); err != nil {
   248  			return err
   249  		}
   250  	}
   251  	return nil
   252  }
   253  
   254  func (it *legacyIter) Reset() {
   255  	if it.scan != nil {
   256  		_ = it.scan.Close()
   257  		it.scan = nil
   258  	}
   259  	if it.cont != nil {
   260  		_ = it.cont.Close()
   261  		it.cont = nil
   262  	}
   263  }
   264  
   265  func (it *legacyIter) Stats() IteratorStats {
   266  	st, _ := it.s.Stats(context.Background())
   267  	return IteratorStats{
   268  		NextCost:     st.NextCost,
   269  		ContainsCost: st.ContainsCost,
   270  		Size:         st.Size.Size,
   271  		ExactSize:    st.Size.Exact,
   272  	}
   273  }
   274  
   275  func (it *legacyIter) Size() (int64, bool) {
   276  	st, _ := it.s.Stats(context.Background())
   277  	return st.Size.Size, st.Size.Exact
   278  }
   279  
   280  func (it *legacyIter) Optimize() (Iterator, bool) {
   281  	nit, ok := it.s.Optimize(context.Background())
   282  	if !ok {
   283  		if it.self != nil {
   284  			return it.self, false
   285  		}
   286  		return it, false
   287  	}
   288  	return AsLegacy(nit), true
   289  }
   290  
   291  func (it *legacyIter) SubIterators() []Iterator {
   292  	its := it.s.SubIterators()
   293  	out := make([]Iterator, 0, len(its))
   294  	for _, s := range its {
   295  		out = append(out, AsLegacy(s))
   296  	}
   297  	return out
   298  }
   299  
   300  func (it *legacyIter) Close() error {
   301  	if it.scan != nil {
   302  		it.scan.Close()
   303  		it.scan = nil
   304  	}
   305  	if it.cont != nil {
   306  		it.cont.Close()
   307  		it.cont = nil
   308  	}
   309  	return nil
   310  }