github.com/cayleygraph/cayley@v0.7.7/graph/gaedatastore/iterator.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 gaedatastore
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  
    21  	"github.com/cayleygraph/cayley/clog"
    22  	"github.com/cayleygraph/cayley/graph"
    23  	"github.com/cayleygraph/quad"
    24  
    25  	"google.golang.org/appengine/datastore"
    26  )
    27  
    28  type Iterator struct {
    29  	size   int64
    30  	dir    quad.Direction
    31  	qs     *QuadStore
    32  	name   string
    33  	isAll  bool
    34  	kind   string
    35  	hash   string
    36  	done   bool
    37  	buffer []string
    38  	offset int
    39  	last   string
    40  	result graph.Ref
    41  	err    error
    42  }
    43  
    44  var (
    45  	bufferSize = 50
    46  )
    47  
    48  func NewIterator(qs *QuadStore, k string, d quad.Direction, val graph.Ref) *Iterator {
    49  	t := val.(*Token)
    50  	if t == nil {
    51  		clog.Errorf("Token == nil")
    52  	}
    53  	if t.Kind != nodeKind {
    54  		clog.Errorf("Cannot create an iterator from a non-node value")
    55  		return &Iterator{done: true}
    56  	}
    57  	if k != nodeKind && k != quadKind {
    58  		clog.Errorf("Cannot create iterator for unknown kind")
    59  		return &Iterator{done: true}
    60  	}
    61  	if qs.context == nil {
    62  		clog.Errorf("Cannot create iterator without a valid context")
    63  		return &Iterator{done: true}
    64  	}
    65  	name := quad.StringOf(qs.NameOf(t))
    66  
    67  	// The number of references to this node is held in the nodes entity
    68  	key := qs.createKeyFromToken(t)
    69  	foundNode := new(NodeEntry)
    70  	err := datastore.Get(qs.context, key, foundNode)
    71  	if err != nil && err != datastore.ErrNoSuchEntity {
    72  		clog.Errorf("Error: %v", err)
    73  		return &Iterator{done: true}
    74  	}
    75  	size := foundNode.Size
    76  
    77  	return &Iterator{
    78  		name:  name,
    79  		dir:   d,
    80  		qs:    qs,
    81  		size:  size,
    82  		isAll: false,
    83  		kind:  k,
    84  		hash:  t.Hash,
    85  		done:  false,
    86  	}
    87  }
    88  
    89  func NewAllIterator(qs *QuadStore, kind string) *Iterator {
    90  	if kind != nodeKind && kind != quadKind {
    91  		clog.Errorf("Cannot create iterator for an unknown kind")
    92  		return &Iterator{done: true}
    93  	}
    94  	if qs.context == nil {
    95  		clog.Errorf("Cannot create iterator without a valid context")
    96  		return &Iterator{done: true}
    97  	}
    98  
    99  	st, err := qs.Stats(context.Background(), true)
   100  	var size int64
   101  	if kind == nodeKind {
   102  		size = st.Nodes.Size
   103  	} else {
   104  		size = st.Quads.Size
   105  	}
   106  	return &Iterator{
   107  		qs:    qs,
   108  		size:  size,
   109  		err:   err,
   110  		dir:   quad.Any,
   111  		isAll: true,
   112  		kind:  kind,
   113  		done:  err != nil,
   114  	}
   115  }
   116  
   117  func (it *Iterator) Reset() {
   118  	it.buffer = nil
   119  	it.offset = 0
   120  	it.done = false
   121  	it.last = ""
   122  	it.result = nil
   123  }
   124  
   125  func (it *Iterator) Close() error {
   126  	it.buffer = nil
   127  	it.offset = 0
   128  	it.done = true
   129  	it.last = ""
   130  	it.result = nil
   131  	return nil
   132  }
   133  
   134  func (it *Iterator) Contains(ctx context.Context, v graph.Ref) bool {
   135  	if it.isAll {
   136  		// The result needs to be set, so when contains is called, the result can be retrieved
   137  		it.result = v
   138  		return true
   139  	}
   140  	t := v.(*Token)
   141  	if t == nil {
   142  		clog.Errorf("Could not cast to token")
   143  		return false
   144  	}
   145  	if t.Kind == nodeKind {
   146  		clog.Errorf("Contains does not work with node values")
   147  		return false
   148  	}
   149  	// Contains is for when you want to know that an iterator refers to a quad
   150  	var offset int
   151  	switch it.dir {
   152  	case quad.Subject:
   153  		offset = 0
   154  	case quad.Predicate:
   155  		offset = (quad.HashSize * 2)
   156  	case quad.Object:
   157  		offset = (quad.HashSize * 2) * 2
   158  	case quad.Label:
   159  		offset = (quad.HashSize * 2) * 3
   160  	}
   161  	val := t.Hash[offset : offset+(quad.HashSize*2)]
   162  	if val == it.hash {
   163  		return true
   164  	}
   165  	return false
   166  }
   167  
   168  func (it *Iterator) TagResults(dst map[string]graph.Ref) {}
   169  
   170  func (it *Iterator) NextPath(ctx context.Context) bool {
   171  	return false
   172  }
   173  
   174  // No subiterators.
   175  func (it *Iterator) SubIterators() []graph.Iterator {
   176  	return nil
   177  }
   178  
   179  func (it *Iterator) Result() graph.Ref {
   180  	return it.result
   181  }
   182  
   183  func (it *Iterator) Next(ctx context.Context) bool {
   184  	if it.offset+1 < len(it.buffer) {
   185  		it.offset++
   186  		it.result = &Token{Kind: it.kind, Hash: it.buffer[it.offset]}
   187  		return true
   188  	}
   189  	if it.done {
   190  		return false
   191  	}
   192  	// Reset buffer and offset
   193  	it.offset = 0
   194  	it.buffer = make([]string, 0, bufferSize)
   195  	// Create query
   196  	// TODO (panamafrancis) Keys only query?
   197  	q := datastore.NewQuery(it.kind).Limit(bufferSize)
   198  	if !it.isAll {
   199  		// Filter on the direction {subject,objekt...}
   200  		q = q.Filter(it.dir.String()+" =", it.name)
   201  	}
   202  	// Get last cursor position
   203  	cursor, err := datastore.DecodeCursor(it.last)
   204  	if err == nil {
   205  		q = q.Start(cursor)
   206  	}
   207  	// Buffer the keys of the next 50 matches
   208  	t := q.Run(it.qs.context)
   209  	for {
   210  		// Quirk of the datastore, you cannot pass a nil value to to Next()
   211  		// even if you just want the keys
   212  		var k *datastore.Key
   213  		skip := false
   214  		if it.kind == quadKind {
   215  			temp := new(QuadEntry)
   216  			k, err = t.Next(temp)
   217  			// Skip if quad has been deleted
   218  			if len(temp.Added) <= len(temp.Deleted) {
   219  				skip = true
   220  			}
   221  		} else {
   222  			temp := new(NodeEntry)
   223  			k, err = t.Next(temp)
   224  			// Skip if node has been deleted
   225  			if temp.Size == 0 {
   226  				skip = true
   227  			}
   228  		}
   229  		if err == datastore.Done {
   230  			it.done = true
   231  			break
   232  		}
   233  		if err != nil {
   234  			clog.Errorf("Error fetching next entry %v", err)
   235  			it.err = err
   236  			return false
   237  		}
   238  		if !skip {
   239  			it.buffer = append(it.buffer, k.StringID())
   240  		}
   241  	}
   242  	// Save cursor position
   243  	cursor, err = t.Cursor()
   244  	if err == nil {
   245  		it.last = cursor.String()
   246  	}
   247  	// Protect against bad queries
   248  	if it.done && len(it.buffer) == 0 {
   249  		clog.Warningf("Query did not return any results")
   250  		return false
   251  	}
   252  	// First result
   253  	it.result = &Token{Kind: it.kind, Hash: it.buffer[it.offset]}
   254  	return true
   255  }
   256  
   257  func (it *Iterator) Err() error {
   258  	return it.err
   259  }
   260  
   261  func (it *Iterator) Size() (int64, bool) {
   262  	return it.size, true
   263  }
   264  
   265  func (it *Iterator) Sorted() bool                     { return false }
   266  func (it *Iterator) Optimize() (graph.Iterator, bool) { return it, false }
   267  func (it *Iterator) String() string {
   268  	return fmt.Sprintf("GAE(%s/%s)", it.name, it.hash)
   269  }
   270  
   271  // TODO (panamafrancis) calculate costs
   272  func (it *Iterator) Stats() graph.IteratorStats {
   273  	size, exact := it.Size()
   274  	return graph.IteratorStats{
   275  		ContainsCost: 1,
   276  		NextCost:     5,
   277  		Size:         size,
   278  		ExactSize:    exact,
   279  	}
   280  }
   281  
   282  var _ graph.Iterator = &Iterator{}