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

     1  package graph
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  
     8  	"github.com/cayleygraph/cayley/clog"
     9  	"github.com/cayleygraph/quad"
    10  )
    11  
    12  // IterateChain is a chain-enabled helper to setup iterator execution.
    13  type IterateChain struct {
    14  	ctx context.Context
    15  	s   IteratorShape
    16  	it  Scanner
    17  	qs  QuadStore
    18  
    19  	paths    bool
    20  	optimize bool
    21  
    22  	limit int
    23  	n     int
    24  }
    25  
    26  // Iterate is a set of helpers for iteration. Context may be used to cancel execution.
    27  // Iterator will be optimized and closed after execution.
    28  //
    29  // By default, iteration has no limit and includes sub-paths.
    30  func Iterate(ctx context.Context, it Iterator) *IterateChain {
    31  	if ctx == nil {
    32  		ctx = context.Background()
    33  	}
    34  	return &IterateChain{
    35  		ctx: ctx, s: AsShape(it),
    36  		limit: -1, paths: true,
    37  		optimize: true,
    38  	}
    39  }
    40  func (c *IterateChain) next() bool {
    41  	select {
    42  	case <-c.ctx.Done():
    43  		return false
    44  	default:
    45  	}
    46  	ok := (c.limit < 0 || c.n < c.limit) && c.it.Next(c.ctx)
    47  	if ok {
    48  		c.n++
    49  	}
    50  	return ok
    51  }
    52  func (c *IterateChain) nextPath() bool {
    53  	select {
    54  	case <-c.ctx.Done():
    55  		return false
    56  	default:
    57  	}
    58  	ok := c.paths && (c.limit < 0 || c.n < c.limit) && c.it.NextPath(c.ctx)
    59  	if ok {
    60  		c.n++
    61  	}
    62  	return ok
    63  }
    64  func (c *IterateChain) start() {
    65  	if c.optimize {
    66  		c.s, _ = c.s.Optimize(c.ctx)
    67  	}
    68  	c.it = c.s.Iterate()
    69  	if !clog.V(2) {
    70  		return
    71  	}
    72  	if b, err := json.MarshalIndent(DescribeIterator(AsLegacy(c.s)), "", "  "); err != nil {
    73  		clog.Infof("failed to format description: %v", err)
    74  	} else {
    75  		clog.Infof("%s", b)
    76  	}
    77  }
    78  func (c *IterateChain) end() {
    79  	c.it.Close()
    80  	if !clog.V(2) {
    81  		return
    82  	}
    83  	if b, err := json.MarshalIndent(DumpStats(AsLegacy(c.s)), "", "  "); err != nil {
    84  		clog.Infof("failed to format stats: %v", err)
    85  	} else {
    86  		clog.Infof("%s", b)
    87  	}
    88  }
    89  
    90  // Limit limits a total number of results returned.
    91  func (c *IterateChain) Limit(n int) *IterateChain {
    92  	c.limit = n
    93  	return c
    94  }
    95  
    96  // Paths switches iteration over sub-paths (with it.NextPath).
    97  // Defaults to true.
    98  func (c *IterateChain) Paths(enable bool) *IterateChain {
    99  	c.paths = enable
   100  	return c
   101  }
   102  
   103  // On sets a default quad store for iteration. If qs was set, it may be omitted in other functions.
   104  func (c *IterateChain) On(qs QuadStore) *IterateChain {
   105  	c.qs = qs
   106  	return c
   107  }
   108  
   109  // UnOptimized disables iterator optimization.
   110  func (c *IterateChain) UnOptimized() *IterateChain {
   111  	c.optimize = false
   112  	return c
   113  }
   114  
   115  // Each will run a provided callback for each result of the iterator.
   116  func (c *IterateChain) Each(fnc func(Ref)) error {
   117  	c.start()
   118  	defer c.end()
   119  	done := c.ctx.Done()
   120  
   121  	for c.next() {
   122  		select {
   123  		case <-done:
   124  			return c.ctx.Err()
   125  		default:
   126  		}
   127  		fnc(c.it.Result())
   128  		for c.nextPath() {
   129  			select {
   130  			case <-done:
   131  				return c.ctx.Err()
   132  			default:
   133  			}
   134  			fnc(c.it.Result())
   135  		}
   136  	}
   137  	return c.it.Err()
   138  }
   139  
   140  // All will return all results of an iterator.
   141  func (c *IterateChain) Count() (int64, error) {
   142  	// TODO(dennwc): this should wrap the shape in Count
   143  	if c.optimize {
   144  		c.s, _ = c.s.Optimize(c.ctx)
   145  	}
   146  	if st, err := c.s.Stats(c.ctx); err != nil {
   147  		return st.Size.Size, err
   148  	} else if st.Size.Exact {
   149  		return st.Size.Size, nil
   150  	}
   151  	c.start()
   152  	defer c.end()
   153  	if err := c.it.Err(); err != nil {
   154  		return 0, err
   155  	}
   156  	done := c.ctx.Done()
   157  	var cnt int64
   158  iteration:
   159  	for c.next() {
   160  		select {
   161  		case <-done:
   162  			break iteration
   163  		default:
   164  		}
   165  		cnt++
   166  		for c.nextPath() {
   167  			select {
   168  			case <-done:
   169  				break iteration
   170  			default:
   171  			}
   172  			cnt++
   173  		}
   174  	}
   175  	return cnt, c.it.Err()
   176  }
   177  
   178  // All will return all results of an iterator.
   179  func (c *IterateChain) All() ([]Ref, error) {
   180  	c.start()
   181  	defer c.end()
   182  	done := c.ctx.Done()
   183  	var out []Ref
   184  iteration:
   185  	for c.next() {
   186  		select {
   187  		case <-done:
   188  			break iteration
   189  		default:
   190  		}
   191  		out = append(out, c.it.Result())
   192  		for c.nextPath() {
   193  			select {
   194  			case <-done:
   195  				break iteration
   196  			default:
   197  			}
   198  			out = append(out, c.it.Result())
   199  		}
   200  	}
   201  	return out, c.it.Err()
   202  }
   203  
   204  // First will return a first result of an iterator. It returns nil if iterator is empty.
   205  func (c *IterateChain) First() (Ref, error) {
   206  	c.start()
   207  	defer c.end()
   208  	if !c.next() {
   209  		return nil, c.it.Err()
   210  	}
   211  	return c.it.Result(), nil
   212  }
   213  
   214  // Send will send each result of the iterator to the provided channel.
   215  //
   216  // Channel will NOT be closed when function returns.
   217  func (c *IterateChain) Send(out chan<- Ref) error {
   218  	c.start()
   219  	defer c.end()
   220  	done := c.ctx.Done()
   221  	for c.next() {
   222  		select {
   223  		case <-done:
   224  			return c.ctx.Err()
   225  		case out <- c.it.Result():
   226  		}
   227  		for c.nextPath() {
   228  			select {
   229  			case <-done:
   230  				return c.ctx.Err()
   231  			case out <- c.it.Result():
   232  			}
   233  		}
   234  	}
   235  	return c.it.Err()
   236  }
   237  
   238  // TagEach will run a provided tag map callback for each result of the iterator.
   239  func (c *IterateChain) TagEach(fnc func(map[string]Ref)) error {
   240  	c.start()
   241  	defer c.end()
   242  	done := c.ctx.Done()
   243  
   244  	mn := 0
   245  	for c.next() {
   246  		select {
   247  		case <-done:
   248  			return c.ctx.Err()
   249  		default:
   250  		}
   251  		tags := make(map[string]Ref, mn)
   252  		c.it.TagResults(tags)
   253  		if n := len(tags); n > mn {
   254  			mn = n
   255  		}
   256  		fnc(tags)
   257  		for c.nextPath() {
   258  			select {
   259  			case <-done:
   260  				return c.ctx.Err()
   261  			default:
   262  			}
   263  			tags := make(map[string]Ref, mn)
   264  			c.it.TagResults(tags)
   265  			if n := len(tags); n > mn {
   266  				mn = n
   267  			}
   268  			fnc(tags)
   269  		}
   270  	}
   271  	return c.it.Err()
   272  }
   273  
   274  var errNoQuadStore = fmt.Errorf("no quad store in Iterate")
   275  
   276  // EachValue is an analog of Each, but it will additionally call NameOf
   277  // for each graph.Ref before passing it to a callback.
   278  func (c *IterateChain) EachValue(qs QuadStore, fnc func(quad.Value)) error {
   279  	if qs != nil {
   280  		c.qs = qs
   281  	}
   282  	if c.qs == nil {
   283  		return errNoQuadStore
   284  	}
   285  	// TODO(dennwc): batch NameOf?
   286  	return c.Each(func(v Ref) {
   287  		if nv := c.qs.NameOf(v); nv != nil {
   288  			fnc(nv)
   289  		}
   290  	})
   291  }
   292  
   293  // EachValuePair is an analog of Each, but it will additionally call NameOf
   294  // for each graph.Ref before passing it to a callback. Original value will be passed as well.
   295  func (c *IterateChain) EachValuePair(qs QuadStore, fnc func(Ref, quad.Value)) error {
   296  	if qs != nil {
   297  		c.qs = qs
   298  	}
   299  	if c.qs == nil {
   300  		return errNoQuadStore
   301  	}
   302  	// TODO(dennwc): batch NameOf?
   303  	return c.Each(func(v Ref) {
   304  		if nv := c.qs.NameOf(v); nv != nil {
   305  			fnc(v, nv)
   306  		}
   307  	})
   308  }
   309  
   310  // AllValues is an analog of All, but it will additionally call NameOf
   311  // for each graph.Ref before returning the results slice.
   312  func (c *IterateChain) AllValues(qs QuadStore) ([]quad.Value, error) {
   313  	var out []quad.Value
   314  	err := c.EachValue(qs, func(v quad.Value) {
   315  		out = append(out, v)
   316  	})
   317  	return out, err
   318  }
   319  
   320  // FirstValue is an analog of First, but it does lookup of a value in QuadStore.
   321  func (c *IterateChain) FirstValue(qs QuadStore) (quad.Value, error) {
   322  	if qs != nil {
   323  		c.qs = qs
   324  	}
   325  	if c.qs == nil {
   326  		return nil, errNoQuadStore
   327  	}
   328  	v, err := c.First()
   329  	if err != nil || v == nil {
   330  		return nil, err
   331  	}
   332  	// TODO: return an error from NameOf once we have it exposed
   333  	return c.qs.NameOf(v), nil
   334  }
   335  
   336  // SendValues is an analog of Send, but it will additionally call NameOf
   337  // for each graph.Ref before sending it to a channel.
   338  func (c *IterateChain) SendValues(qs QuadStore, out chan<- quad.Value) error {
   339  	if qs != nil {
   340  		c.qs = qs
   341  	}
   342  	if c.qs == nil {
   343  		return errNoQuadStore
   344  	}
   345  	c.start()
   346  	defer c.end()
   347  	done := c.ctx.Done()
   348  	send := func(v Ref) error {
   349  		nv := c.qs.NameOf(c.it.Result())
   350  		if nv == nil {
   351  			return nil
   352  		}
   353  		select {
   354  		case <-done:
   355  			return c.ctx.Err()
   356  		case out <- c.qs.NameOf(c.it.Result()):
   357  		}
   358  		return nil
   359  	}
   360  	for c.next() {
   361  		if err := send(c.it.Result()); err != nil {
   362  			return err
   363  		}
   364  		for c.nextPath() {
   365  			if err := send(c.it.Result()); err != nil {
   366  				return err
   367  			}
   368  		}
   369  	}
   370  	return c.it.Err()
   371  }
   372  
   373  // TagValues is an analog of TagEach, but it will additionally call NameOf
   374  // for each graph.Ref before passing the map to a callback.
   375  func (c *IterateChain) TagValues(qs QuadStore, fnc func(map[string]quad.Value)) error {
   376  	if qs != nil {
   377  		c.qs = qs
   378  	}
   379  	if c.qs == nil {
   380  		return errNoQuadStore
   381  	}
   382  	return c.TagEach(func(m map[string]Ref) {
   383  		vm := make(map[string]quad.Value, len(m))
   384  		for k, v := range m {
   385  			vm[k] = c.qs.NameOf(v) // TODO(dennwc): batch NameOf?
   386  		}
   387  		fnc(vm)
   388  	})
   389  }