github.com/cayleygraph/cayley@v0.7.7/graph/iterator/materialize.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  // A simple iterator that, when first called Contains() or Next() upon, materializes the whole subiterator, stores it locally, and responds. Essentially a cache.
    18  
    19  import (
    20  	"context"
    21  
    22  	"github.com/cayleygraph/cayley/clog"
    23  	"github.com/cayleygraph/cayley/graph"
    24  )
    25  
    26  const MaterializeLimit = 1000
    27  
    28  type result struct {
    29  	id   graph.Ref
    30  	tags map[string]graph.Ref
    31  }
    32  
    33  var _ graph.IteratorFuture = &Materialize{}
    34  
    35  type Materialize struct {
    36  	it *materialize
    37  	graph.Iterator
    38  }
    39  
    40  func NewMaterialize(sub graph.Iterator) *Materialize {
    41  	it := &Materialize{
    42  		it: newMaterialize(graph.AsShape(sub)),
    43  	}
    44  	it.Iterator = graph.NewLegacy(it.it, it)
    45  	return it
    46  }
    47  
    48  func NewMaterializeWithSize(sub graph.Iterator, size int64) *Materialize {
    49  	it := &Materialize{
    50  		it: newMaterializeWithSize(graph.AsShape(sub), size),
    51  	}
    52  	it.Iterator = graph.NewLegacy(it.it, it)
    53  	return it
    54  }
    55  
    56  func (it *Materialize) AsShape() graph.IteratorShape {
    57  	it.Close()
    58  	return it.it
    59  }
    60  
    61  var _ graph.IteratorShapeCompat = &materialize{}
    62  
    63  type materialize struct {
    64  	sub        graph.IteratorShape
    65  	expectSize int64
    66  }
    67  
    68  func newMaterialize(sub graph.IteratorShape) *materialize {
    69  	return newMaterializeWithSize(sub, 0)
    70  }
    71  
    72  func newMaterializeWithSize(sub graph.IteratorShape, size int64) *materialize {
    73  	return &materialize{
    74  		sub:        sub,
    75  		expectSize: size,
    76  	}
    77  }
    78  
    79  func (it *materialize) Iterate() graph.Scanner {
    80  	return newMaterializeNext(it.sub)
    81  }
    82  
    83  func (it *materialize) Lookup() graph.Index {
    84  	return newMaterializeContains(it.sub)
    85  }
    86  
    87  func (it *materialize) AsLegacy() graph.Iterator {
    88  	it2 := &Materialize{it: it}
    89  	it2.Iterator = graph.NewLegacy(it, it2)
    90  	return it2
    91  }
    92  
    93  func (it *materialize) String() string {
    94  	return "Materialize"
    95  }
    96  
    97  func (it *materialize) SubIterators() []graph.IteratorShape {
    98  	return []graph.IteratorShape{it.sub}
    99  }
   100  
   101  func (it *materialize) Optimize(ctx context.Context) (graph.IteratorShape, bool) {
   102  	newSub, changed := it.sub.Optimize(ctx)
   103  	if changed {
   104  		it.sub = newSub
   105  		if IsNull2(it.sub) {
   106  			return it.sub, true
   107  		}
   108  	}
   109  	return it, false
   110  }
   111  
   112  // The entire point of Materialize is to amortize the cost by
   113  // putting it all up front.
   114  func (it *materialize) Stats(ctx context.Context) (graph.IteratorCosts, error) {
   115  	overhead := int64(2)
   116  	var size graph.Size
   117  	subitStats, err := it.sub.Stats(ctx)
   118  	if it.expectSize > 0 {
   119  		size = graph.Size{Size: it.expectSize, Exact: false}
   120  	} else {
   121  		size = subitStats.Size
   122  	}
   123  	return graph.IteratorCosts{
   124  		ContainsCost: overhead * subitStats.NextCost,
   125  		NextCost:     overhead * subitStats.NextCost,
   126  		Size:         size,
   127  	}, err
   128  }
   129  
   130  type materializeNext struct {
   131  	sub  graph.IteratorShape
   132  	next graph.Scanner
   133  
   134  	containsMap map[interface{}]int
   135  	values      [][]result
   136  	index       int
   137  	subindex    int
   138  	hasRun      bool
   139  	aborted     bool
   140  	err         error
   141  }
   142  
   143  func newMaterializeNext(sub graph.IteratorShape) *materializeNext {
   144  	return &materializeNext{
   145  		containsMap: make(map[interface{}]int),
   146  		sub:         sub,
   147  		next:        sub.Iterate(),
   148  		index:       -1,
   149  	}
   150  }
   151  
   152  func (it *materializeNext) Close() error {
   153  	it.containsMap = nil
   154  	it.values = nil
   155  	it.hasRun = false
   156  	return it.next.Close()
   157  }
   158  
   159  func (it *materializeNext) TagResults(dst map[string]graph.Ref) {
   160  	if !it.hasRun {
   161  		return
   162  	}
   163  	if it.aborted {
   164  		it.next.TagResults(dst)
   165  		return
   166  	}
   167  	if it.Result() == nil {
   168  		return
   169  	}
   170  	for tag, value := range it.values[it.index][it.subindex].tags {
   171  		dst[tag] = value
   172  	}
   173  }
   174  
   175  func (it *materializeNext) String() string {
   176  	return "Materialize"
   177  }
   178  
   179  func (it *materializeNext) Result() graph.Ref {
   180  	if it.aborted {
   181  		return it.next.Result()
   182  	}
   183  	if len(it.values) == 0 {
   184  		return nil
   185  	}
   186  	if it.index == -1 {
   187  		return nil
   188  	}
   189  	if it.index >= len(it.values) {
   190  		return nil
   191  	}
   192  	return it.values[it.index][it.subindex].id
   193  }
   194  
   195  func (it *materializeNext) Next(ctx context.Context) bool {
   196  	if !it.hasRun {
   197  		it.materializeSet(ctx)
   198  	}
   199  	if it.err != nil {
   200  		return false
   201  	}
   202  	if it.aborted {
   203  		n := it.next.Next(ctx)
   204  		it.err = it.next.Err()
   205  		return n
   206  	}
   207  
   208  	it.index++
   209  	it.subindex = 0
   210  	if it.index >= len(it.values) {
   211  		return false
   212  	}
   213  	return true
   214  }
   215  
   216  func (it *materializeNext) Err() error {
   217  	return it.err
   218  }
   219  
   220  func (it *materializeNext) NextPath(ctx context.Context) bool {
   221  	if !it.hasRun {
   222  		it.materializeSet(ctx)
   223  	}
   224  	if it.err != nil {
   225  		return false
   226  	}
   227  	if it.aborted {
   228  		return it.next.NextPath(ctx)
   229  	}
   230  
   231  	it.subindex++
   232  	if it.subindex >= len(it.values[it.index]) {
   233  		// Don't go off the end of the world
   234  		it.subindex--
   235  		return false
   236  	}
   237  	return true
   238  }
   239  
   240  func (it *materializeNext) materializeSet(ctx context.Context) {
   241  	i := 0
   242  	mn := 0
   243  	for it.next.Next(ctx) {
   244  		i++
   245  		if i > MaterializeLimit {
   246  			it.aborted = true
   247  			break
   248  		}
   249  		id := it.next.Result()
   250  		val := graph.ToKey(id)
   251  		if _, ok := it.containsMap[val]; !ok {
   252  			it.containsMap[val] = len(it.values)
   253  			it.values = append(it.values, nil)
   254  		}
   255  		index := it.containsMap[val]
   256  		tags := make(map[string]graph.Ref, mn)
   257  		it.next.TagResults(tags)
   258  		if n := len(tags); n > mn {
   259  			n = mn
   260  		}
   261  		it.values[index] = append(it.values[index], result{id: id, tags: tags})
   262  		for it.next.NextPath(ctx) {
   263  			i++
   264  			if i > MaterializeLimit {
   265  				it.aborted = true
   266  				break
   267  			}
   268  			tags := make(map[string]graph.Ref, mn)
   269  			it.next.TagResults(tags)
   270  			if n := len(tags); n > mn {
   271  				n = mn
   272  			}
   273  			it.values[index] = append(it.values[index], result{id: id, tags: tags})
   274  		}
   275  	}
   276  	it.err = it.next.Err()
   277  	if it.err == nil && it.aborted {
   278  		if clog.V(2) {
   279  			clog.Infof("Aborting subiterator")
   280  		}
   281  		it.values = nil
   282  		it.containsMap = nil
   283  		_ = it.next.Close()
   284  		it.next = it.sub.Iterate()
   285  	}
   286  	it.hasRun = true
   287  }
   288  
   289  type materializeContains struct {
   290  	next *materializeNext
   291  	sub  graph.Index // only set if aborted
   292  }
   293  
   294  func newMaterializeContains(sub graph.IteratorShape) *materializeContains {
   295  	return &materializeContains{
   296  		next: newMaterializeNext(sub),
   297  	}
   298  }
   299  
   300  func (it *materializeContains) Close() error {
   301  	err := it.next.Close()
   302  	if it.sub != nil {
   303  		if err2 := it.sub.Close(); err2 != nil && err == nil {
   304  			err = err2
   305  		}
   306  	}
   307  	return err
   308  }
   309  
   310  func (it *materializeContains) TagResults(dst map[string]graph.Ref) {
   311  	if it.sub != nil {
   312  		it.sub.TagResults(dst)
   313  		return
   314  	}
   315  	it.next.TagResults(dst)
   316  }
   317  
   318  func (it *materializeContains) String() string {
   319  	return "MaterializeContains"
   320  }
   321  
   322  func (it *materializeContains) Result() graph.Ref {
   323  	if it.sub != nil {
   324  		return it.sub.Result()
   325  	}
   326  	return it.next.Result()
   327  }
   328  
   329  func (it *materializeContains) Err() error {
   330  	if err := it.next.Err(); err != nil {
   331  		return err
   332  	} else if it.sub == nil {
   333  		return nil
   334  	}
   335  	return it.sub.Err()
   336  }
   337  
   338  func (it *materializeContains) run(ctx context.Context) {
   339  	it.next.materializeSet(ctx)
   340  	if it.next.aborted {
   341  		it.sub = it.next.sub.Lookup()
   342  	}
   343  }
   344  
   345  func (it *materializeContains) Contains(ctx context.Context, v graph.Ref) bool {
   346  	if !it.next.hasRun {
   347  		it.run(ctx)
   348  	}
   349  	if it.next.Err() != nil {
   350  		return false
   351  	}
   352  	if it.sub != nil {
   353  		return it.sub.Contains(ctx, v)
   354  	}
   355  	key := graph.ToKey(v)
   356  	if i, ok := it.next.containsMap[key]; ok {
   357  		it.next.index = i
   358  		it.next.subindex = 0
   359  		return true
   360  	}
   361  	return false
   362  }
   363  
   364  func (it *materializeContains) NextPath(ctx context.Context) bool {
   365  	if !it.next.hasRun {
   366  		it.run(ctx)
   367  	}
   368  	if it.next.Err() != nil {
   369  		return false
   370  	}
   371  	if it.sub != nil {
   372  		return it.sub.NextPath(ctx)
   373  	}
   374  	return it.next.NextPath(ctx)
   375  }