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 16package iterator 17 18import ( 19 "context" 20 21 "github.com/cayleygraph/cayley/graph" 22) 23 24var _ graph.Iterator = &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. 28type And struct { 29 uid uint64 30 tags graph.Tagger 31 internalIterators []graph.Iterator 32 itCount int 33 primaryIt graph.Iterator 34 checkList []graph.Iterator 35 result graph.Value 36 runstats graph.IteratorStats 37 err error 38 qs graph.QuadStore 39} 40 41// NewAnd creates an And iterator. `qs` is only required when needing a handle 42// for QuadStore-specific optimizations, otherwise nil is acceptable. 43func NewAnd(qs graph.QuadStore, sub ...graph.Iterator) *And { 44 it := &And{ 45 uid: NextUID(), 46 internalIterators: make([]graph.Iterator, 0, 20), 47 qs: qs, 48 } 49 for _, s := range sub { 50 it.AddSubIterator(s) 51 } 52 return it 53} 54 55func (it *And) UID() uint64 { 56 return it.uid 57} 58 59// Reset all internal iterators 60func (it *And) Reset() { 61 it.result = nil 62 it.primaryIt.Reset() 63 for _, sub := range it.internalIterators { 64 sub.Reset() 65 } 66 it.checkList = nil 67} 68 69func (it *And) Tagger() *graph.Tagger { 70 return &it.tags 71} 72 73// An extended TagResults, as it needs to add it's own results and 74// recurse down it's subiterators. 75func (it *And) TagResults(dst map[string]graph.Value) { 76 it.tags.TagResult(dst, it.Result()) 77 78 if it.primaryIt != nil { 79 it.primaryIt.TagResults(dst) 80 } 81 for _, sub := range it.internalIterators { 82 sub.TagResults(dst) 83 } 84} 85 86func (it *And) Clone() graph.Iterator { 87 and := NewAnd(it.qs) 88 and.AddSubIterator(it.primaryIt.Clone()) 89 and.tags.CopyFrom(it) 90 for _, sub := range it.internalIterators { 91 and.AddSubIterator(sub.Clone()) 92 } 93 if it.checkList != nil { 94 and.optimizeContains() 95 } 96 return and 97} 98 99// Returns a slice of the subiterators, in order (primary iterator first). 100func (it *And) SubIterators() []graph.Iterator { 101 iters := make([]graph.Iterator, 0, len(it.internalIterators)+1) 102 if it.primaryIt != nil { 103 iters = append(iters, it.primaryIt) 104 } 105 iters = append(iters, it.internalIterators...) 106 return iters 107} 108 109func (it *And) String() string { 110 return "And" 111} 112 113// Add a subiterator to this And iterator. 114// 115// The first iterator that is added becomes the primary iterator. This is 116// important. Calling Optimize() is the way to change the order based on 117// subiterator statistics. Without Optimize(), the order added is the order 118// used. 119func (it *And) AddSubIterator(sub graph.Iterator) { 120 if it.itCount > 0 { 121 it.internalIterators = append(it.internalIterators, sub) 122 it.itCount++ 123 return 124 } 125 it.primaryIt = sub 126 it.itCount++ 127} 128 129// Returns advances the And iterator. Because the And is the intersection of its 130// subiterators, it must choose one subiterator to produce a candidate, and check 131// this value against the subiterators. A productive choice of primary iterator 132// is therefore very important. 133func (it *And) Next(ctx context.Context) bool { 134 graph.NextLogIn(it) 135 it.runstats.Next += 1 136 for it.primaryIt.Next(ctx) { 137 curr := it.primaryIt.Result() 138 if it.subItsContain(ctx, curr, nil) { 139 it.result = curr 140 return graph.NextLogOut(it, true) 141 } 142 } 143 it.err = it.primaryIt.Err() 144 return graph.NextLogOut(it, false) 145} 146 147func (it *And) Err() error { 148 if err := it.err; err != nil { 149 return err 150 } 151 if err := it.primaryIt.Err(); err != nil { 152 return err 153 } 154 for _, si := range it.internalIterators { 155 if err := si.Err(); err != nil { 156 return err 157 } 158 } 159 return nil 160} 161 162func (it *And) Result() graph.Value { 163 return it.result 164} 165 166// Checks a value against the non-primary iterators, in order. 167func (it *And) subItsContain(ctx context.Context, val graph.Value, lastResult graph.Value) bool { 168 var subIsGood = true 169 for i, sub := range it.internalIterators { 170 subIsGood = sub.Contains(ctx, val) 171 if !subIsGood { 172 if lastResult != nil { 173 for j := 0; j < i; j++ { 174 it.internalIterators[j].Contains(ctx, lastResult) 175 } 176 } 177 break 178 } 179 } 180 return subIsGood 181} 182 183func (it *And) checkContainsList(ctx context.Context, val graph.Value, lastResult graph.Value) bool { 184 ok := true 185 for i, c := range it.checkList { 186 ok = c.Contains(ctx, val) 187 if !ok { 188 it.err = c.Err() 189 if it.err != nil { 190 return false 191 } 192 193 if lastResult != nil { 194 for j := 0; j < i; j++ { 195 // One of the iterators has determined that this value doesn't 196 // match. However, the iterators that came before in the list 197 // may have returned "ok" to Contains(). We need to set all 198 // the tags back to what the previous result was -- effectively 199 // seeking back exactly one -- so we check all the prior iterators 200 // with the (already verified) result and throw away the result, 201 // which will be 'true' 202 it.checkList[j].Contains(ctx, lastResult) 203 204 it.err = it.checkList[j].Err() 205 if it.err != nil { 206 return false 207 } 208 } 209 } 210 break 211 } 212 } 213 if ok { 214 it.result = val 215 } 216 return graph.ContainsLogOut(it, val, ok) 217} 218 219// Check a value against the entire iterator, in order. 220func (it *And) Contains(ctx context.Context, val graph.Value) bool { 221 graph.ContainsLogIn(it, val) 222 it.runstats.Contains += 1 223 lastResult := it.result 224 if it.checkList != nil { 225 return it.checkContainsList(ctx, val, lastResult) 226 } 227 mainGood := it.primaryIt.Contains(ctx, val) 228 if mainGood { 229 othersGood := it.subItsContain(ctx, val, lastResult) 230 if othersGood { 231 it.result = val 232 return graph.ContainsLogOut(it, val, true) 233 } 234 } 235 if lastResult != nil { 236 it.primaryIt.Contains(ctx, lastResult) 237 } 238 return graph.ContainsLogOut(it, val, false) 239} 240 241// Returns the approximate size of the And iterator. Because we're dealing 242// with an intersection, we know that the largest we can be is the size of the 243// smallest iterator. This is the heuristic we shall follow. Better heuristics 244// welcome. 245func (it *And) Size() (int64, bool) { 246 val, b := it.primaryIt.Size() 247 for _, sub := range it.internalIterators { 248 newval, newb := sub.Size() 249 if val > newval { 250 val = newval 251 } 252 b = newb && b 253 } 254 return val, b 255} 256 257// An And has no NextPath of its own -- that is, there are no other values 258// which satisfy our previous result that are not the result itself. Our 259// subiterators might, however, so just pass the call recursively. 260func (it *And) NextPath(ctx context.Context) bool { 261 if it.primaryIt.NextPath(ctx) { 262 return true 263 } 264 it.err = it.primaryIt.Err() 265 if it.err != nil { 266 return false 267 } 268 for _, sub := range it.internalIterators { 269 if sub.NextPath(ctx) { 270 return true 271 } 272 273 it.err = sub.Err() 274 if it.err != nil { 275 return false 276 } 277 } 278 return false 279} 280 281// Perform and-specific cleanup, of which there currently is none. 282func (it *And) cleanUp() {} 283 284// Close this iterator, and, by extension, close the subiterators. 285// Close should be idempotent, and it follows that if it's subiterators 286// follow this contract, the And follows the contract. It closes all 287// subiterators it can, but returns the first error it encounters. 288func (it *And) Close() error { 289 it.cleanUp() 290 291 err := it.primaryIt.Close() 292 for _, sub := range it.internalIterators { 293 _err := sub.Close() 294 if _err != nil && err == nil { 295 err = _err 296 } 297 } 298 299 return err 300} 301 302// Register this as an "and" iterator. 303func (it *And) Type() graph.Type { return graph.And } 304