iterator.go 1.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. package redis
  2. import (
  3. "context"
  4. "sync"
  5. )
  6. // ScanIterator is used to incrementally iterate over a collection of elements.
  7. // It's safe for concurrent use by multiple goroutines.
  8. type ScanIterator struct {
  9. mu sync.Mutex // protects Scanner and pos
  10. cmd *ScanCmd
  11. pos int
  12. }
  13. // Err returns the last iterator error, if any.
  14. func (it *ScanIterator) Err() error {
  15. it.mu.Lock()
  16. err := it.cmd.Err()
  17. it.mu.Unlock()
  18. return err
  19. }
  20. // Next advances the cursor and returns true if more values can be read.
  21. func (it *ScanIterator) Next(ctx context.Context) bool {
  22. it.mu.Lock()
  23. defer it.mu.Unlock()
  24. // Instantly return on errors.
  25. if it.cmd.Err() != nil {
  26. return false
  27. }
  28. // Advance cursor, check if we are still within range.
  29. if it.pos < len(it.cmd.page) {
  30. it.pos++
  31. return true
  32. }
  33. for {
  34. // Return if there is no more data to fetch.
  35. if it.cmd.cursor == 0 {
  36. return false
  37. }
  38. // Fetch next page.
  39. switch it.cmd.args[0] {
  40. case "scan", "qscan":
  41. it.cmd.args[1] = it.cmd.cursor
  42. default:
  43. it.cmd.args[2] = it.cmd.cursor
  44. }
  45. err := it.cmd.process(ctx, it.cmd)
  46. if err != nil {
  47. return false
  48. }
  49. it.pos = 1
  50. // Redis can occasionally return empty page.
  51. if len(it.cmd.page) > 0 {
  52. return true
  53. }
  54. }
  55. }
  56. // Val returns the key/field at the current cursor position.
  57. func (it *ScanIterator) Val() string {
  58. var v string
  59. it.mu.Lock()
  60. if it.cmd.Err() == nil && it.pos > 0 && it.pos <= len(it.cmd.page) {
  61. v = it.cmd.page[it.pos-1]
  62. }
  63. it.mu.Unlock()
  64. return v
  65. }