github.com/cayleygraph/cayley@v0.7.7/graph/iterator/or.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 // Defines the or and short-circuiting or iterator. Or is the union operator for it's subiterators. 18 // Short-circuiting-or is a little different. It will return values from the first graph.iterator that returns 19 // values at all, and then stops. 20 // 21 // Never reorders the iterators from the order they arrive. It is either the union or the first one. 22 // May return the same value twice -- once for each branch. 23 24 import ( 25 "context" 26 27 "github.com/cayleygraph/cayley/graph" 28 ) 29 30 var _ graph.IteratorFuture = &Or{} 31 32 type Or struct { 33 it *or 34 graph.Iterator 35 } 36 37 func NewOr(sub ...graph.Iterator) *Or { 38 in := make([]graph.IteratorShape, 0, len(sub)) 39 for _, s := range sub { 40 in = append(in, graph.AsShape(s)) 41 } 42 it := &Or{ 43 it: newOr(in...), 44 } 45 it.Iterator = graph.NewLegacy(it.it, it) 46 return it 47 } 48 49 func NewShortCircuitOr(sub ...graph.Iterator) *Or { 50 in := make([]graph.IteratorShape, 0, len(sub)) 51 for _, s := range sub { 52 in = append(in, graph.AsShape(s)) 53 } 54 it := &Or{ 55 it: newShortCircuitOr(in...), 56 } 57 it.Iterator = graph.NewLegacy(it.it, it) 58 return it 59 } 60 61 func (it *Or) AsShape() graph.IteratorShape { 62 it.Close() 63 return it.it 64 } 65 66 // Add a subiterator to this Or graph.iterator. Order matters. 67 func (it *Or) AddSubIterator(sub graph.Iterator) { 68 it.it.AddSubIterator(graph.AsShape(sub)) 69 } 70 71 var _ graph.IteratorShapeCompat = &or{} 72 73 type or struct { 74 isShortCircuiting bool 75 sub []graph.IteratorShape 76 curInd int 77 result graph.Ref 78 err error 79 } 80 81 func newOr(sub ...graph.IteratorShape) *or { 82 it := &or{ 83 sub: make([]graph.IteratorShape, 0, 20), 84 curInd: -1, 85 } 86 for _, s := range sub { 87 it.AddSubIterator(s) 88 } 89 return it 90 } 91 92 func newShortCircuitOr(sub ...graph.IteratorShape) *or { 93 it := &or{ 94 sub: make([]graph.IteratorShape, 0, 20), 95 isShortCircuiting: true, 96 curInd: -1, 97 } 98 for _, s := range sub { 99 it.AddSubIterator(s) 100 } 101 return it 102 } 103 104 func (it *or) Iterate() graph.Scanner { 105 sub := make([]graph.Scanner, 0, len(it.sub)) 106 for _, s := range it.sub { 107 sub = append(sub, s.Iterate()) 108 } 109 return newOrNext(sub, it.isShortCircuiting) 110 } 111 112 func (it *or) Lookup() graph.Index { 113 sub := make([]graph.Index, 0, len(it.sub)) 114 for _, s := range it.sub { 115 sub = append(sub, s.Lookup()) 116 } 117 return newOrContains(sub, it.isShortCircuiting) 118 } 119 120 func (it *or) AsLegacy() graph.Iterator { 121 it2 := &Or{it: it} 122 it2.Iterator = graph.NewLegacy(it, it2) 123 return it2 124 } 125 126 // Returns a list.List of the subiterators, in order. The returned slice must not be modified. 127 func (it *or) SubIterators() []graph.IteratorShape { 128 return it.sub 129 } 130 131 func (it *or) String() string { 132 return "Or" 133 } 134 135 // Add a subiterator to this Or graph.iterator. Order matters. 136 func (it *or) AddSubIterator(sub graph.IteratorShape) { 137 it.sub = append(it.sub, sub) 138 } 139 140 func (it *or) Optimize(ctx context.Context) (graph.IteratorShape, bool) { 141 old := it.SubIterators() 142 optIts := optimizeSubIterators2(ctx, old) 143 newOr := newOr() 144 newOr.isShortCircuiting = it.isShortCircuiting 145 146 // Add the subiterators in order. 147 for _, o := range optIts { 148 newOr.AddSubIterator(o) 149 } 150 return newOr, true 151 } 152 153 // Returns the approximate size of the Or graph.iterator. Because we're dealing 154 // with a union, we know that the largest we can be is the sum of all the iterators, 155 // or in the case of short-circuiting, the longest. 156 func (it *or) Stats(ctx context.Context) (graph.IteratorCosts, error) { 157 ContainsCost := int64(0) 158 NextCost := int64(0) 159 Size := graph.Size{ 160 Size: 0, 161 Exact: true, 162 } 163 var last error 164 for _, sub := range it.sub { 165 stats, err := sub.Stats(ctx) 166 if err != nil { 167 last = err 168 } 169 NextCost += stats.NextCost 170 ContainsCost += stats.ContainsCost 171 if it.isShortCircuiting { 172 if Size.Size < stats.Size.Size { 173 Size = stats.Size 174 } 175 } else { 176 Size.Size += stats.Size.Size 177 Size.Exact = Size.Exact && stats.Size.Exact 178 } 179 } 180 return graph.IteratorCosts{ 181 ContainsCost: ContainsCost, 182 NextCost: NextCost, 183 Size: Size, 184 }, last 185 } 186 187 type orNext struct { 188 shortCircuit bool 189 sub []graph.Scanner 190 curInd int 191 result graph.Ref 192 err error 193 } 194 195 func newOrNext(sub []graph.Scanner, shortCircuit bool) *orNext { 196 return &orNext{ 197 sub: sub, 198 curInd: -1, 199 shortCircuit: shortCircuit, 200 } 201 } 202 203 // Overrides BaseIterator TagResults, as it needs to add it's own results and 204 // recurse down it's subiterators. 205 func (it *orNext) TagResults(dst map[string]graph.Ref) { 206 it.sub[it.curInd].TagResults(dst) 207 } 208 209 func (it *orNext) String() string { 210 return "OrNext" 211 } 212 213 // Next advances the Or graph.iterator. Because the Or is the union of its 214 // subiterators, it must produce from all subiterators -- unless it it 215 // shortcircuiting, in which case, it is the first one that returns anything. 216 func (it *orNext) Next(ctx context.Context) bool { 217 if it.curInd >= len(it.sub) { 218 return false 219 } 220 var first bool 221 for { 222 if it.curInd == -1 { 223 it.curInd = 0 224 first = true 225 } 226 curIt := it.sub[it.curInd] 227 228 if curIt.Next(ctx) { 229 it.result = curIt.Result() 230 return true 231 } 232 233 it.err = curIt.Err() 234 if it.err != nil { 235 return false 236 } 237 238 if it.shortCircuit && !first { 239 break 240 } 241 it.curInd++ 242 if it.curInd >= len(it.sub) { 243 break 244 } 245 } 246 247 return false 248 } 249 250 func (it *orNext) Err() error { 251 return it.err 252 } 253 254 func (it *orNext) Result() graph.Ref { 255 return it.result 256 } 257 258 // An Or has no NextPath of its own -- that is, there are no other values 259 // which satisfy our previous result that are not the result itself. Our 260 // subiterators might, however, so just pass the call recursively. In the case of 261 // shortcircuiting, only allow new results from the currently checked graph.iterator 262 func (it *orNext) NextPath(ctx context.Context) bool { 263 if it.curInd != -1 { 264 currIt := it.sub[it.curInd] 265 ok := currIt.NextPath(ctx) 266 if !ok { 267 it.err = currIt.Err() 268 } 269 return ok 270 } 271 return false 272 } 273 274 // Close this graph.iterator, and, by extension, close the subiterators. 275 // Close should be idempotent, and it follows that if it's subiterators 276 // follow this contract, the Or follows the contract. It closes all 277 // subiterators it can, but returns the first error it encounters. 278 func (it *orNext) Close() error { 279 var err error 280 for _, sub := range it.sub { 281 _err := sub.Close() 282 if _err != nil && err == nil { 283 err = _err 284 } 285 } 286 return err 287 } 288 289 var _ graph.Iterator = &Or{} 290 291 type orContains struct { 292 shortCircuit bool 293 sub []graph.Index 294 curInd int 295 result graph.Ref 296 err error 297 } 298 299 func newOrContains(sub []graph.Index, shortCircuit bool) *orContains { 300 return &orContains{ 301 sub: sub, 302 curInd: -1, 303 shortCircuit: shortCircuit, 304 } 305 } 306 307 // Overrides BaseIterator TagResults, as it needs to add it's own results and 308 // recurse down it's subiterators. 309 func (it *orContains) TagResults(dst map[string]graph.Ref) { 310 it.sub[it.curInd].TagResults(dst) 311 } 312 313 func (it *orContains) String() string { 314 return "OrContains" 315 } 316 317 func (it *orContains) Err() error { 318 return it.err 319 } 320 321 func (it *orContains) Result() graph.Ref { 322 return it.result 323 } 324 325 // Checks a value against the iterators, in order. 326 func (it *orContains) subItsContain(ctx context.Context, val graph.Ref) (bool, error) { 327 subIsGood := false 328 for i, sub := range it.sub { 329 subIsGood = sub.Contains(ctx, val) 330 if subIsGood { 331 it.curInd = i 332 break 333 } 334 335 err := sub.Err() 336 if err != nil { 337 return false, err 338 } 339 } 340 return subIsGood, nil 341 } 342 343 // Check a value against the entire graph.iterator, in order. 344 func (it *orContains) Contains(ctx context.Context, val graph.Ref) bool { 345 anyGood, err := it.subItsContain(ctx, val) 346 if err != nil { 347 it.err = err 348 return false 349 } else if !anyGood { 350 return false 351 } 352 it.result = val 353 return true 354 } 355 356 // An Or has no NextPath of its own -- that is, there are no other values 357 // which satisfy our previous result that are not the result itself. Our 358 // subiterators might, however, so just pass the call recursively. In the case of 359 // shortcircuiting, only allow new results from the currently checked graph.iterator 360 func (it *orContains) NextPath(ctx context.Context) bool { 361 if it.curInd != -1 { 362 currIt := it.sub[it.curInd] 363 ok := currIt.NextPath(ctx) 364 if !ok { 365 it.err = currIt.Err() 366 } 367 return ok 368 } 369 // TODO(dennwc): this should probably list matches from other sub-iterators 370 return false 371 } 372 373 // Close this graph.iterator, and, by extension, close the subiterators. 374 // Close should be idempotent, and it follows that if it's subiterators 375 // follow this contract, the Or follows the contract. It closes all 376 // subiterators it can, but returns the first error it encounters. 377 func (it *orContains) Close() error { 378 var err error 379 for _, sub := range it.sub { 380 _err := sub.Close() 381 if _err != nil && err == nil { 382 err = _err 383 } 384 } 385 return err 386 }