github.com/cayleygraph/cayley@v0.7.7/graph/shape.go (about) 1 package graph 2 3 import "context" 4 5 // Scanner is an iterator that lists all results sequentially, but not necessarily in a sorted order. 6 type Scanner interface { 7 IteratorBase 8 9 // Next advances the iterator to the next value, which will then be available through 10 // the Result method. It returns false if no further advancement is possible, or if an 11 // error was encountered during iteration. Err should be consulted to distinguish 12 // between the two cases. 13 Next(ctx context.Context) bool 14 } 15 16 // Index is an index lookup iterator. It allows to check if an index contains a specific value. 17 type Index interface { 18 IteratorBase 19 20 // Contains returns whether the value is within the set held by the iterator. 21 // 22 // It will set Result to the matching subtree. TagResults can be used to collect values from tree branches. 23 Contains(ctx context.Context, v Ref) bool 24 } 25 26 // Tagger is an interface for iterators that can tag values. Tags are returned as a part of TagResults call. 27 type TaggerShape interface { 28 IteratorShape 29 TaggerBase 30 CopyFromTagger(st TaggerBase) 31 } 32 33 type IteratorCosts struct { 34 ContainsCost int64 35 NextCost int64 36 Size Size 37 } 38 39 // Shape is an iterator shape, similar to a query plan. But the plan is not specific in this 40 // case - it is used to reorder query branches, and the decide what branches will be scanned 41 // and what branches will lookup values (hopefully from the index, but not necessarily). 42 type IteratorShape interface { 43 // TODO(dennwc): merge with shape.Shape 44 45 // String returns a short textual representation of an iterator. 46 String() string 47 48 // Iterate starts this iterator in scanning mode. Resulting iterator will list all 49 // results sequentially, but not necessary in the sorted order. Caller must close 50 // the iterator. 51 Iterate() Scanner 52 53 // Lookup starts this iterator in an index lookup mode. Depending on the iterator type, 54 // this may still involve database scans. Resulting iterator allows to check an index 55 // contains a specified value. Caller must close the iterator. 56 Lookup() Index 57 58 // These methods relate to choosing the right iterator, or optimizing an 59 // iterator tree 60 // 61 // Stats() returns the relative costs of calling the iteration methods for 62 // this iterator, as well as the size. Roughly, it will take NextCost * Size 63 // "cost units" to get everything out of the iterator. This is a wibbly-wobbly 64 // thing, and not exact, but a useful heuristic. 65 Stats(ctx context.Context) (IteratorCosts, error) 66 67 // Optimizes an iterator. Can replace the iterator, or merely move things 68 // around internally. if it chooses to replace it with a better iterator, 69 // returns (the new iterator, true), if not, it returns (self, false). 70 Optimize(ctx context.Context) (IteratorShape, bool) 71 72 // Return a slice of the subiterators for this iterator. 73 SubIterators() []IteratorShape 74 } 75 76 // IteratorShapeCompat is an optional interface for iterator Shape that support direct conversion 77 // to a legacy Iterator. This interface should be avoided an will be deprecated in the future. 78 type IteratorShapeCompat interface { 79 IteratorShape 80 AsLegacy() Iterator 81 } 82 83 // AsShape converts a legacy Iterator to an iterator Shape. 84 func AsShape(it Iterator) IteratorShape { 85 if it == nil { 86 panic("nil iterator") 87 } 88 if it2, ok := it.(IteratorFuture); ok { 89 return it2.AsShape() 90 } 91 return &legacyShape{it} 92 } 93 94 var _ IteratorShapeCompat = &legacyShape{} 95 96 type legacyShape struct { 97 Iterator 98 } 99 100 func (it *legacyShape) Optimize(ctx context.Context) (IteratorShape, bool) { 101 nit, ok := it.Iterator.Optimize() 102 if !ok { 103 return it, false 104 } 105 return AsShape(nit), true 106 } 107 108 func (it *legacyShape) SubIterators() []IteratorShape { 109 its := it.Iterator.SubIterators() 110 out := make([]IteratorShape, 0, len(its)) 111 for _, s := range its { 112 out = append(out, AsShape(s)) 113 } 114 return out 115 } 116 117 func (it *legacyShape) Stats(ctx context.Context) (IteratorCosts, error) { 118 st := it.Iterator.Stats() 119 return IteratorCosts{ 120 NextCost: st.NextCost, 121 ContainsCost: st.ContainsCost, 122 Size: Size{ 123 Size: st.Size, 124 Exact: st.ExactSize, 125 }, 126 }, it.Err() 127 } 128 129 func (it *legacyShape) Iterate() Scanner { 130 it.Reset() 131 return it 132 } 133 func (it *legacyShape) Lookup() Index { 134 it.Reset() 135 return it 136 } 137 func (it *legacyShape) Close() error { 138 // FIXME(dennwc): this is incorrect, but we must do this to prevent closing iterators after 139 // multiple calls to Iterate and/or Lookup 140 return nil 141 } 142 func (it *legacyShape) AsLegacy() Iterator { 143 return it.Iterator 144 } 145 146 // NewLegacy creates a new legacy Iterator from an iterator Shape. 147 // This method will always create a new iterator, while AsLegacy will try to unwrap it first. 148 func NewLegacy(s IteratorShape, self Iterator) Iterator { 149 if s == nil { 150 panic("nil iterator") 151 } 152 return &legacyIter{s: s, self: self} 153 } 154 155 // AsLegacy convert an iterator Shape to a legacy Iterator interface. 156 func AsLegacy(s IteratorShape) Iterator { 157 if it2, ok := s.(IteratorShapeCompat); ok { 158 return it2.AsLegacy() 159 } 160 return NewLegacy(s, nil) 161 } 162 163 var _ IteratorFuture = &legacyIter{} 164 165 type legacyIter struct { 166 s IteratorShape 167 self Iterator 168 scan Scanner 169 cont Index 170 } 171 172 func (it *legacyIter) String() string { 173 return it.s.String() 174 } 175 176 func (it *legacyIter) AsShape() IteratorShape { 177 it.Close() 178 return it.s 179 } 180 181 func (it *legacyIter) TagResults(m map[string]Ref) { 182 if it.cont != nil && it.scan != nil { 183 panic("both iterators are set") 184 } 185 if it.scan != nil { 186 it.scan.TagResults(m) 187 } else if it.cont != nil { 188 it.cont.TagResults(m) 189 } 190 } 191 192 func (it *legacyIter) Result() Ref { 193 if it.cont != nil && it.scan != nil { 194 panic("both iterators are set") 195 } 196 if it.scan != nil { 197 return it.scan.Result() 198 } 199 if it.cont != nil { 200 return it.cont.Result() 201 } 202 return nil 203 } 204 205 func (it *legacyIter) Next(ctx context.Context) bool { 206 if it.scan == nil { 207 if it.cont != nil { 208 panic("attempt to set a scan iterator on contains") 209 } 210 it.scan = it.s.Iterate() 211 } 212 return it.scan.Next(ctx) 213 } 214 215 func (it *legacyIter) NextPath(ctx context.Context) bool { 216 if it.cont != nil && it.scan != nil { 217 panic("both iterators are set") 218 } 219 if it.scan != nil { 220 return it.scan.NextPath(ctx) 221 } 222 if it.cont != nil { 223 return it.cont.NextPath(ctx) 224 } 225 panic("calling NextPath before Next or Contains") 226 } 227 228 func (it *legacyIter) Contains(ctx context.Context, v Ref) bool { 229 if it.cont == nil { 230 // reset iterator by default 231 if it.scan != nil { 232 it.scan.Close() 233 it.scan = nil 234 } 235 it.cont = it.s.Lookup() 236 } 237 return it.cont.Contains(ctx, v) 238 } 239 240 func (it *legacyIter) Err() error { 241 if it.scan != nil { 242 if err := it.scan.Err(); err != nil { 243 return err 244 } 245 } 246 if it.cont != nil { 247 if err := it.cont.Err(); err != nil { 248 return err 249 } 250 } 251 return nil 252 } 253 254 func (it *legacyIter) Reset() { 255 if it.scan != nil { 256 _ = it.scan.Close() 257 it.scan = nil 258 } 259 if it.cont != nil { 260 _ = it.cont.Close() 261 it.cont = nil 262 } 263 } 264 265 func (it *legacyIter) Stats() IteratorStats { 266 st, _ := it.s.Stats(context.Background()) 267 return IteratorStats{ 268 NextCost: st.NextCost, 269 ContainsCost: st.ContainsCost, 270 Size: st.Size.Size, 271 ExactSize: st.Size.Exact, 272 } 273 } 274 275 func (it *legacyIter) Size() (int64, bool) { 276 st, _ := it.s.Stats(context.Background()) 277 return st.Size.Size, st.Size.Exact 278 } 279 280 func (it *legacyIter) Optimize() (Iterator, bool) { 281 nit, ok := it.s.Optimize(context.Background()) 282 if !ok { 283 if it.self != nil { 284 return it.self, false 285 } 286 return it, false 287 } 288 return AsLegacy(nit), true 289 } 290 291 func (it *legacyIter) SubIterators() []Iterator { 292 its := it.s.SubIterators() 293 out := make([]Iterator, 0, len(its)) 294 for _, s := range its { 295 out = append(out, AsLegacy(s)) 296 } 297 return out 298 } 299 300 func (it *legacyIter) Close() error { 301 if it.scan != nil { 302 it.scan.Close() 303 it.scan = nil 304 } 305 if it.cont != nil { 306 it.cont.Close() 307 it.cont = nil 308 } 309 return nil 310 }