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{}