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 }