github.com/cayleygraph/cayley@v0.7.7/graph/iterator/and.go (about) 1 // Defines the And iterator, one of the base iterators. And requires no 2 // knowledge of the constituent QuadStore; its sole purpose is to act as an 3 // intersection operator across the subiterators it is given. If one iterator 4 // contains [1,3,5] and another [2,3,4] -- then And is an iterator that 5 // 'contains' [3] 6 // 7 // It accomplishes this in one of two ways. If it is a Next()ed iterator (that 8 // is, it is a top level iterator, or on the "Next() path", then it will Next() 9 // it's primary iterator (helpfully, and.primary_it) and Contains() the resultant 10 // value against it's other iterators. If it matches all of them, then it 11 // returns that value. Otherwise, it repeats the process. 12 // 13 // If it's on a Contains() path, it merely Contains()s every iterator, and returns the 14 // logical AND of each result. 15 16 package iterator 17 18 import ( 19 "context" 20 21 "github.com/cayleygraph/cayley/graph" 22 ) 23 24 var _ graph.IteratorFuture = &And{} 25 26 // The And iterator. Consists of a number of subiterators, the primary of which will 27 // be Next()ed if next is called. 28 type And struct { 29 it *and 30 graph.Iterator 31 } 32 33 // NewAnd creates an And iterator. `qs` is only required when needing a handle 34 // for QuadStore-specific optimizations, otherwise nil is acceptable. 35 func NewAnd(sub ...graph.Iterator) *And { 36 it := &And{ 37 it: newAnd(), 38 } 39 for _, s := range sub { 40 it.it.AddSubIterator(graph.AsShape(s)) 41 } 42 it.Iterator = graph.NewLegacy(it.it, it) 43 return it 44 } 45 46 func (it *And) AsShape() graph.IteratorShape { 47 it.Close() 48 return it.it 49 } 50 51 // Add a subiterator to this And iterator. 52 // 53 // The first iterator that is added becomes the primary iterator. This is 54 // important. Calling Optimize() is the way to change the order based on 55 // subiterator statistics. Without Optimize(), the order added is the order 56 // used. 57 func (it *And) AddSubIterator(sub graph.Iterator) { 58 it.it.AddSubIterator(graph.AsShape(sub)) 59 } 60 61 // AddOptionalIterator adds an iterator that will only be Contain'ed and will not affect iteration results. 62 // Only tags will be propagated from this iterator. 63 func (it *And) AddOptionalIterator(sub graph.Iterator) *And { 64 it.it.AddOptionalIterator(graph.AsShape(sub)) 65 return it 66 } 67 68 var _ graph.IteratorShapeCompat = &and{} 69 70 // The And iterator. Consists of a number of subiterators, the primary of which will 71 // be Next()ed if next is called. 72 type and struct { 73 sub []graph.IteratorShape 74 checkList []graph.IteratorShape // special order for Contains 75 opt []graph.IteratorShape 76 } 77 78 // NewAnd creates an And iterator. `qs` is only required when needing a handle 79 // for QuadStore-specific optimizations, otherwise nil is acceptable. 80 func newAnd(sub ...graph.IteratorShape) *and { 81 it := &and{ 82 sub: make([]graph.IteratorShape, 0, 20), 83 } 84 for _, s := range sub { 85 it.AddSubIterator(s) 86 } 87 return it 88 } 89 90 func (it *and) Iterate() graph.Scanner { 91 if len(it.sub) == 0 { 92 return newNull().Iterate() 93 } 94 sub := make([]graph.Index, 0, len(it.sub)-1) 95 for _, s := range it.sub[1:] { 96 sub = append(sub, s.Lookup()) 97 } 98 opt := make([]graph.Index, 0, len(it.opt)) 99 for _, s := range it.opt { 100 opt = append(opt, s.Lookup()) 101 } 102 return newAndNext(it.sub[0].Iterate(), newAndContains(sub, opt)) 103 } 104 105 func (it *and) Lookup() graph.Index { 106 if len(it.sub) == 0 { 107 return newNull().Lookup() 108 } 109 sub := make([]graph.Index, 0, len(it.sub)) 110 check := it.checkList 111 if check == nil { 112 check = it.sub 113 } 114 for _, s := range check { 115 sub = append(sub, s.Lookup()) 116 } 117 opt := make([]graph.Index, 0, len(it.opt)) 118 for _, s := range it.opt { 119 opt = append(opt, s.Lookup()) 120 } 121 return newAndContains(sub, opt) 122 } 123 124 func (it *and) AsLegacy() graph.Iterator { 125 it2 := &And{it: it} 126 it2.Iterator = graph.NewLegacy(it, it2) 127 return it2 128 } 129 130 // Returns a slice of the subiterators, in order (primary iterator first). 131 func (it *and) SubIterators() []graph.IteratorShape { 132 iters := make([]graph.IteratorShape, 0, len(it.sub)+len(it.opt)) 133 iters = append(iters, it.sub...) 134 iters = append(iters, it.opt...) 135 return iters 136 } 137 138 func (it *and) String() string { 139 return "And" 140 } 141 142 // Add a subiterator to this And iterator. 143 // 144 // The first iterator that is added becomes the primary iterator. This is 145 // important. Calling Optimize() is the way to change the order based on 146 // subiterator statistics. Without Optimize(), the order added is the order 147 // used. 148 func (it *and) AddSubIterator(sub graph.IteratorShape) { 149 if sub == nil { 150 panic("nil iterator") 151 } 152 it.sub = append(it.sub, sub) 153 } 154 155 // AddOptionalIterator adds an iterator that will only be Contain'ed and will not affect iteration results. 156 // Only tags will be propagated from this iterator. 157 func (it *and) AddOptionalIterator(sub graph.IteratorShape) *and { 158 it.opt = append(it.opt, sub) 159 return it 160 } 161 162 // The And iterator. Consists of a number of subiterators, the primary of which will 163 // be Next()ed if next is called. 164 type andNext struct { 165 primary graph.Scanner 166 secondary graph.Index 167 result graph.Ref 168 } 169 170 // NewAnd creates an And iterator. `qs` is only required when needing a handle 171 // for QuadStore-specific optimizations, otherwise nil is acceptable. 172 func newAndNext(pri graph.Scanner, sec graph.Index) graph.Scanner { 173 return &andNext{ 174 primary: pri, 175 secondary: sec, 176 } 177 } 178 179 // An extended TagResults, as it needs to add it's own results and 180 // recurse down it's subiterators. 181 func (it *andNext) TagResults(dst map[string]graph.Ref) { 182 it.primary.TagResults(dst) 183 it.secondary.TagResults(dst) 184 } 185 186 func (it *andNext) String() string { 187 return "AndNext" 188 } 189 190 // Returns advances the And iterator. Because the And is the intersection of its 191 // subiterators, it must choose one subiterator to produce a candidate, and check 192 // this value against the subiterators. A productive choice of primary iterator 193 // is therefore very important. 194 func (it *andNext) Next(ctx context.Context) bool { 195 for it.primary.Next(ctx) { 196 cur := it.primary.Result() 197 if it.secondary.Contains(ctx, cur) { 198 it.result = cur 199 return true 200 } 201 } 202 return false 203 } 204 205 func (it *andNext) Err() error { 206 if err := it.primary.Err(); err != nil { 207 return err 208 } 209 if err := it.secondary.Err(); err != nil { 210 return err 211 } 212 return nil 213 } 214 215 func (it *andNext) Result() graph.Ref { 216 return it.result 217 } 218 219 // An And has no NextPath of its own -- that is, there are no other values 220 // which satisfy our previous result that are not the result itself. Our 221 // subiterators might, however, so just pass the call recursively. 222 func (it *andNext) NextPath(ctx context.Context) bool { 223 if it.primary.NextPath(ctx) { 224 return true 225 } else if err := it.primary.Err(); err != nil { 226 return false 227 } 228 if it.secondary.NextPath(ctx) { 229 return true 230 } else if err := it.secondary.Err(); err != nil { 231 return false 232 } 233 return false 234 } 235 236 // Close this iterator, and, by extension, close the subiterators. 237 // Close should be idempotent, and it follows that if it's subiterators 238 // follow this contract, the And follows the contract. It closes all 239 // subiterators it can, but returns the first error it encounters. 240 func (it *andNext) Close() error { 241 err := it.primary.Close() 242 if err2 := it.secondary.Close(); err2 != nil && err == nil { 243 err = err2 244 } 245 return err 246 } 247 248 // The And iterator. Consists of a number of subiterators, the primary of which will 249 // be Next()ed if next is called. 250 type andContains struct { 251 base graph.IteratorShape 252 sub []graph.Index 253 opt []graph.Index 254 optCheck []bool 255 256 result graph.Ref 257 err error 258 } 259 260 // NewAnd creates an And iterator. `qs` is only required when needing a handle 261 // for QuadStore-specific optimizations, otherwise nil is acceptable. 262 func newAndContains(sub, opt []graph.Index) graph.Index { 263 return &andContains{ 264 sub: sub, 265 opt: opt, optCheck: make([]bool, len(opt)), 266 } 267 } 268 269 // An extended TagResults, as it needs to add it's own results and 270 // recurse down it's subiterators. 271 func (it *andContains) TagResults(dst map[string]graph.Ref) { 272 for _, sub := range it.sub { 273 sub.TagResults(dst) 274 } 275 for i, sub := range it.opt { 276 if !it.optCheck[i] { 277 continue 278 } 279 sub.TagResults(dst) 280 } 281 } 282 283 func (it *andContains) String() string { 284 return "AndContains" 285 } 286 287 func (it *andContains) Err() error { 288 if err := it.err; err != nil { 289 return err 290 } 291 for _, si := range it.sub { 292 if err := si.Err(); err != nil { 293 return err 294 } 295 } 296 for _, si := range it.opt { 297 if err := si.Err(); err != nil { 298 return err 299 } 300 } 301 return nil 302 } 303 304 func (it *andContains) Result() graph.Ref { 305 return it.result 306 } 307 308 // Check a value against the entire iterator, in order. 309 func (it *andContains) Contains(ctx context.Context, val graph.Ref) bool { 310 prev := it.result 311 for i, sub := range it.sub { 312 if !sub.Contains(ctx, val) { 313 if err := sub.Err(); err != nil { 314 it.err = err 315 return false 316 } 317 // One of the iterators has determined that this value doesn't 318 // match. However, the iterators that came before in the list 319 // may have returned "ok" to Contains(). We need to set all 320 // the tags back to what the previous result was -- effectively 321 // seeking back exactly one -- so we check all the prior iterators 322 // with the (already verified) result and throw away the result, 323 // which will be 'true' 324 if prev != nil { 325 for j := 0; j < i; j++ { 326 it.sub[j].Contains(ctx, prev) 327 if err := it.sub[j].Err(); err != nil { 328 it.err = err 329 return false 330 } 331 } 332 } 333 return false 334 } 335 } 336 it.result = val 337 for i, sub := range it.opt { 338 // remember if we will need to call TagResults on it, nothing more 339 it.optCheck[i] = sub.Contains(ctx, val) 340 } 341 return true 342 } 343 344 // An And has no NextPath of its own -- that is, there are no other values 345 // which satisfy our previous result that are not the result itself. Our 346 // subiterators might, however, so just pass the call recursively. 347 func (it *andContains) NextPath(ctx context.Context) bool { 348 for _, sub := range it.sub { 349 if sub.NextPath(ctx) { 350 return true 351 } else if err := sub.Err(); err != nil { 352 it.err = err 353 return false 354 } 355 } 356 for i, sub := range it.opt { 357 if !it.optCheck[i] { 358 continue 359 } 360 if sub.NextPath(ctx) { 361 return true 362 } else if err := sub.Err(); err != nil { 363 it.err = err 364 return false 365 } 366 } 367 return false 368 } 369 370 // Close this iterator, and, by extension, close the subiterators. 371 // Close should be idempotent, and it follows that if it's subiterators 372 // follow this contract, the And follows the contract. It closes all 373 // subiterators it can, but returns the first error it encounters. 374 func (it *andContains) Close() error { 375 var err error 376 for _, sub := range it.sub { 377 if err2 := sub.Close(); err2 != nil && err == nil { 378 err = err2 379 } 380 } 381 for _, sub := range it.opt { 382 if err2 := sub.Close(); err2 != nil && err == nil { 383 err = err2 384 } 385 } 386 return err 387 }