github.com/cayleygraph/cayley@v0.7.7/graph/iterate.go (about) 1 package graph 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 8 "github.com/cayleygraph/cayley/clog" 9 "github.com/cayleygraph/quad" 10 ) 11 12 // IterateChain is a chain-enabled helper to setup iterator execution. 13 type IterateChain struct { 14 ctx context.Context 15 s IteratorShape 16 it Scanner 17 qs QuadStore 18 19 paths bool 20 optimize bool 21 22 limit int 23 n int 24 } 25 26 // Iterate is a set of helpers for iteration. Context may be used to cancel execution. 27 // Iterator will be optimized and closed after execution. 28 // 29 // By default, iteration has no limit and includes sub-paths. 30 func Iterate(ctx context.Context, it Iterator) *IterateChain { 31 if ctx == nil { 32 ctx = context.Background() 33 } 34 return &IterateChain{ 35 ctx: ctx, s: AsShape(it), 36 limit: -1, paths: true, 37 optimize: true, 38 } 39 } 40 func (c *IterateChain) next() bool { 41 select { 42 case <-c.ctx.Done(): 43 return false 44 default: 45 } 46 ok := (c.limit < 0 || c.n < c.limit) && c.it.Next(c.ctx) 47 if ok { 48 c.n++ 49 } 50 return ok 51 } 52 func (c *IterateChain) nextPath() bool { 53 select { 54 case <-c.ctx.Done(): 55 return false 56 default: 57 } 58 ok := c.paths && (c.limit < 0 || c.n < c.limit) && c.it.NextPath(c.ctx) 59 if ok { 60 c.n++ 61 } 62 return ok 63 } 64 func (c *IterateChain) start() { 65 if c.optimize { 66 c.s, _ = c.s.Optimize(c.ctx) 67 } 68 c.it = c.s.Iterate() 69 if !clog.V(2) { 70 return 71 } 72 if b, err := json.MarshalIndent(DescribeIterator(AsLegacy(c.s)), "", " "); err != nil { 73 clog.Infof("failed to format description: %v", err) 74 } else { 75 clog.Infof("%s", b) 76 } 77 } 78 func (c *IterateChain) end() { 79 c.it.Close() 80 if !clog.V(2) { 81 return 82 } 83 if b, err := json.MarshalIndent(DumpStats(AsLegacy(c.s)), "", " "); err != nil { 84 clog.Infof("failed to format stats: %v", err) 85 } else { 86 clog.Infof("%s", b) 87 } 88 } 89 90 // Limit limits a total number of results returned. 91 func (c *IterateChain) Limit(n int) *IterateChain { 92 c.limit = n 93 return c 94 } 95 96 // Paths switches iteration over sub-paths (with it.NextPath). 97 // Defaults to true. 98 func (c *IterateChain) Paths(enable bool) *IterateChain { 99 c.paths = enable 100 return c 101 } 102 103 // On sets a default quad store for iteration. If qs was set, it may be omitted in other functions. 104 func (c *IterateChain) On(qs QuadStore) *IterateChain { 105 c.qs = qs 106 return c 107 } 108 109 // UnOptimized disables iterator optimization. 110 func (c *IterateChain) UnOptimized() *IterateChain { 111 c.optimize = false 112 return c 113 } 114 115 // Each will run a provided callback for each result of the iterator. 116 func (c *IterateChain) Each(fnc func(Ref)) error { 117 c.start() 118 defer c.end() 119 done := c.ctx.Done() 120 121 for c.next() { 122 select { 123 case <-done: 124 return c.ctx.Err() 125 default: 126 } 127 fnc(c.it.Result()) 128 for c.nextPath() { 129 select { 130 case <-done: 131 return c.ctx.Err() 132 default: 133 } 134 fnc(c.it.Result()) 135 } 136 } 137 return c.it.Err() 138 } 139 140 // All will return all results of an iterator. 141 func (c *IterateChain) Count() (int64, error) { 142 // TODO(dennwc): this should wrap the shape in Count 143 if c.optimize { 144 c.s, _ = c.s.Optimize(c.ctx) 145 } 146 if st, err := c.s.Stats(c.ctx); err != nil { 147 return st.Size.Size, err 148 } else if st.Size.Exact { 149 return st.Size.Size, nil 150 } 151 c.start() 152 defer c.end() 153 if err := c.it.Err(); err != nil { 154 return 0, err 155 } 156 done := c.ctx.Done() 157 var cnt int64 158 iteration: 159 for c.next() { 160 select { 161 case <-done: 162 break iteration 163 default: 164 } 165 cnt++ 166 for c.nextPath() { 167 select { 168 case <-done: 169 break iteration 170 default: 171 } 172 cnt++ 173 } 174 } 175 return cnt, c.it.Err() 176 } 177 178 // All will return all results of an iterator. 179 func (c *IterateChain) All() ([]Ref, error) { 180 c.start() 181 defer c.end() 182 done := c.ctx.Done() 183 var out []Ref 184 iteration: 185 for c.next() { 186 select { 187 case <-done: 188 break iteration 189 default: 190 } 191 out = append(out, c.it.Result()) 192 for c.nextPath() { 193 select { 194 case <-done: 195 break iteration 196 default: 197 } 198 out = append(out, c.it.Result()) 199 } 200 } 201 return out, c.it.Err() 202 } 203 204 // First will return a first result of an iterator. It returns nil if iterator is empty. 205 func (c *IterateChain) First() (Ref, error) { 206 c.start() 207 defer c.end() 208 if !c.next() { 209 return nil, c.it.Err() 210 } 211 return c.it.Result(), nil 212 } 213 214 // Send will send each result of the iterator to the provided channel. 215 // 216 // Channel will NOT be closed when function returns. 217 func (c *IterateChain) Send(out chan<- Ref) error { 218 c.start() 219 defer c.end() 220 done := c.ctx.Done() 221 for c.next() { 222 select { 223 case <-done: 224 return c.ctx.Err() 225 case out <- c.it.Result(): 226 } 227 for c.nextPath() { 228 select { 229 case <-done: 230 return c.ctx.Err() 231 case out <- c.it.Result(): 232 } 233 } 234 } 235 return c.it.Err() 236 } 237 238 // TagEach will run a provided tag map callback for each result of the iterator. 239 func (c *IterateChain) TagEach(fnc func(map[string]Ref)) error { 240 c.start() 241 defer c.end() 242 done := c.ctx.Done() 243 244 mn := 0 245 for c.next() { 246 select { 247 case <-done: 248 return c.ctx.Err() 249 default: 250 } 251 tags := make(map[string]Ref, mn) 252 c.it.TagResults(tags) 253 if n := len(tags); n > mn { 254 mn = n 255 } 256 fnc(tags) 257 for c.nextPath() { 258 select { 259 case <-done: 260 return c.ctx.Err() 261 default: 262 } 263 tags := make(map[string]Ref, mn) 264 c.it.TagResults(tags) 265 if n := len(tags); n > mn { 266 mn = n 267 } 268 fnc(tags) 269 } 270 } 271 return c.it.Err() 272 } 273 274 var errNoQuadStore = fmt.Errorf("no quad store in Iterate") 275 276 // EachValue is an analog of Each, but it will additionally call NameOf 277 // for each graph.Ref before passing it to a callback. 278 func (c *IterateChain) EachValue(qs QuadStore, fnc func(quad.Value)) error { 279 if qs != nil { 280 c.qs = qs 281 } 282 if c.qs == nil { 283 return errNoQuadStore 284 } 285 // TODO(dennwc): batch NameOf? 286 return c.Each(func(v Ref) { 287 if nv := c.qs.NameOf(v); nv != nil { 288 fnc(nv) 289 } 290 }) 291 } 292 293 // EachValuePair is an analog of Each, but it will additionally call NameOf 294 // for each graph.Ref before passing it to a callback. Original value will be passed as well. 295 func (c *IterateChain) EachValuePair(qs QuadStore, fnc func(Ref, quad.Value)) error { 296 if qs != nil { 297 c.qs = qs 298 } 299 if c.qs == nil { 300 return errNoQuadStore 301 } 302 // TODO(dennwc): batch NameOf? 303 return c.Each(func(v Ref) { 304 if nv := c.qs.NameOf(v); nv != nil { 305 fnc(v, nv) 306 } 307 }) 308 } 309 310 // AllValues is an analog of All, but it will additionally call NameOf 311 // for each graph.Ref before returning the results slice. 312 func (c *IterateChain) AllValues(qs QuadStore) ([]quad.Value, error) { 313 var out []quad.Value 314 err := c.EachValue(qs, func(v quad.Value) { 315 out = append(out, v) 316 }) 317 return out, err 318 } 319 320 // FirstValue is an analog of First, but it does lookup of a value in QuadStore. 321 func (c *IterateChain) FirstValue(qs QuadStore) (quad.Value, error) { 322 if qs != nil { 323 c.qs = qs 324 } 325 if c.qs == nil { 326 return nil, errNoQuadStore 327 } 328 v, err := c.First() 329 if err != nil || v == nil { 330 return nil, err 331 } 332 // TODO: return an error from NameOf once we have it exposed 333 return c.qs.NameOf(v), nil 334 } 335 336 // SendValues is an analog of Send, but it will additionally call NameOf 337 // for each graph.Ref before sending it to a channel. 338 func (c *IterateChain) SendValues(qs QuadStore, out chan<- quad.Value) error { 339 if qs != nil { 340 c.qs = qs 341 } 342 if c.qs == nil { 343 return errNoQuadStore 344 } 345 c.start() 346 defer c.end() 347 done := c.ctx.Done() 348 send := func(v Ref) error { 349 nv := c.qs.NameOf(c.it.Result()) 350 if nv == nil { 351 return nil 352 } 353 select { 354 case <-done: 355 return c.ctx.Err() 356 case out <- c.qs.NameOf(c.it.Result()): 357 } 358 return nil 359 } 360 for c.next() { 361 if err := send(c.it.Result()); err != nil { 362 return err 363 } 364 for c.nextPath() { 365 if err := send(c.it.Result()); err != nil { 366 return err 367 } 368 } 369 } 370 return c.it.Err() 371 } 372 373 // TagValues is an analog of TagEach, but it will additionally call NameOf 374 // for each graph.Ref before passing the map to a callback. 375 func (c *IterateChain) TagValues(qs QuadStore, fnc func(map[string]quad.Value)) error { 376 if qs != nil { 377 c.qs = qs 378 } 379 if c.qs == nil { 380 return errNoQuadStore 381 } 382 return c.TagEach(func(m map[string]Ref) { 383 vm := make(map[string]quad.Value, len(m)) 384 for k, v := range m { 385 vm[k] = c.qs.NameOf(v) // TODO(dennwc): batch NameOf? 386 } 387 fnc(vm) 388 }) 389 }