package iterator import ( "context" "fmt" "github.com/cayleygraph/cayley/graph/refs" ) // Skip iterator will skip certain number of values from primary iterator. type Skip struct { skip int64 primaryIt Shape } func NewSkip(primaryIt Shape, off int64) *Skip { return &Skip{ skip: off, primaryIt: primaryIt, } } func (it *Skip) Iterate() Scanner { return newSkipNext(it.primaryIt.Iterate(), it.skip) } func (it *Skip) Lookup() Index { return newSkipContains(it.primaryIt.Lookup(), it.skip) } // SubIterators returns a slice of the sub iterators. func (it *Skip) SubIterators() []Shape { return []Shape{it.primaryIt} } func (it *Skip) Optimize(ctx context.Context) (Shape, bool) { optimizedPrimaryIt, optimized := it.primaryIt.Optimize(ctx) if it.skip == 0 { // nothing to skip return optimizedPrimaryIt, true } it.primaryIt = optimizedPrimaryIt return it, optimized } func (it *Skip) Stats(ctx context.Context) (Costs, error) { primaryStats, err := it.primaryIt.Stats(ctx) if primaryStats.Size.Exact { primaryStats.Size.Value -= it.skip if primaryStats.Size.Value < 0 { primaryStats.Size.Value = 0 } } return primaryStats, err } func (it *Skip) String() string { return fmt.Sprintf("Skip(%d)", it.skip) } // Skip iterator will skip certain number of values from primary iterator. type skipNext struct { skip int64 skipped int64 primaryIt Scanner } func newSkipNext(primaryIt Scanner, skip int64) *skipNext { return &skipNext{ skip: skip, primaryIt: primaryIt, } } func (it *skipNext) TagResults(dst map[string]refs.Ref) { it.primaryIt.TagResults(dst) } // Next advances the Skip iterator. It will skip all initial values // before returning actual result. func (it *skipNext) Next(ctx context.Context) bool { for ; it.skipped < it.skip; it.skipped++ { if !it.primaryIt.Next(ctx) { return false } } if it.primaryIt.Next(ctx) { return true } return false } func (it *skipNext) Err() error { return it.primaryIt.Err() } func (it *skipNext) Result() refs.Ref { return it.primaryIt.Result() } // NextPath checks whether there is another path. It will skip first paths // according to iterator parameter. func (it *skipNext) NextPath(ctx context.Context) bool { for ; it.skipped < it.skip; it.skipped++ { if !it.primaryIt.NextPath(ctx) { return false } } return it.primaryIt.NextPath(ctx) } // Close closes the primary and all iterators. It closes all subiterators // it can, but returns the first error it encounters. func (it *skipNext) Close() error { return it.primaryIt.Close() } func (it *skipNext) String() string { return fmt.Sprintf("SkipNext(%d)", it.skip) } // Skip iterator will skip certain number of values from primary iterator. type skipContains struct { skip int64 skipped int64 primaryIt Index } func newSkipContains(primaryIt Index, skip int64) *skipContains { return &skipContains{ skip: skip, primaryIt: primaryIt, } } func (it *skipContains) TagResults(dst map[string]refs.Ref) { it.primaryIt.TagResults(dst) } func (it *skipContains) Err() error { return it.primaryIt.Err() } func (it *skipContains) Result() refs.Ref { return it.primaryIt.Result() } func (it *skipContains) Contains(ctx context.Context, val refs.Ref) bool { inNextPath := false for it.skipped <= it.skip { // skipping main iterator results inNextPath = false if !it.primaryIt.Contains(ctx, val) { return false } it.skipped++ // TODO(dennwc): we don't really know if we should call NextPath or not, // and there is no good way to know if it.skipped <= it.skip { // skipping NextPath results inNextPath = true if !it.primaryIt.NextPath(ctx) { // main path exists, but we skipped it // and we skipped all alternative paths now // so we definitely "don't have" this value return false } it.skipped++ for it.skipped <= it.skip { if !it.primaryIt.NextPath(ctx) { return false } it.skipped++ } } } if inNextPath && it.primaryIt.NextPath(ctx) { return true } return it.primaryIt.Contains(ctx, val) } // NextPath checks whether there is another path. It will skip first paths // according to iterator parameter. func (it *skipContains) NextPath(ctx context.Context) bool { for ; it.skipped < it.skip; it.skipped++ { if !it.primaryIt.NextPath(ctx) { return false } } return it.primaryIt.NextPath(ctx) } // Close closes the primary and all iterators. It closes all subiterators // it can, but returns the first error it encounters. func (it *skipContains) Close() error { return it.primaryIt.Close() } func (it *skipContains) String() string { return fmt.Sprintf("SkipContains(%d)", it.skip) }