forked from mediocregopher/radix
-
Notifications
You must be signed in to change notification settings - Fork 0
/
scanner.go
148 lines (123 loc) · 3.31 KB
/
scanner.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package radix
import (
"bufio"
"strconv"
"strings"
"errors"
"github.com/mediocregopher/radix/v3/resp/resp2"
)
// Scanner is used to iterate through the results of a SCAN call (or HSCAN,
// SSCAN, etc...)
//
// Once created, repeatedly call Next() on it to fill the passed in string
// pointer with the next result. Next will return false if there's no more
// results to retrieve or if an error occurred, at which point Close should be
// called to retrieve any error.
type Scanner interface {
Next(*string) bool
Close() error
}
// ScanOpts are various parameters which can be passed into ScanWithOpts. Some
// fields are required depending on which type of scan is being done.
type ScanOpts struct {
// The scan command to do, e.g. "SCAN", "HSCAN", etc...
Command string
// The key to perform the scan on. Only necessary when Command isn't "SCAN"
Key string
// An optional pattern to filter returned keys by
Pattern string
// An optional count hint to send to redis to indicate number of keys to
// return per call. This does not affect the actual results of the scan
// command, but it may be useful for optimizing certain datasets
Count int
// An optional type name to filter for values of the given type.
// The type names are the same as returned by the "TYPE" command.
// This if only available in Redis 6 or newer and only works with "SCAN".
// If used with an older version of Redis or with a Command other than
// "SCAN", scanning will fail.
Type string
}
func (o ScanOpts) cmd(rcv interface{}, cursor string) CmdAction {
cmdStr := strings.ToUpper(o.Command)
args := make([]string, 0, 8)
if cmdStr != "SCAN" {
args = append(args, o.Key)
}
args = append(args, cursor)
if o.Pattern != "" {
args = append(args, "MATCH", o.Pattern)
}
if o.Count > 0 {
args = append(args, "COUNT", strconv.Itoa(o.Count))
}
if o.Type != "" {
args = append(args, "TYPE", o.Type)
}
return Cmd(rcv, cmdStr, args...)
}
// ScanAllKeys is a shortcut ScanOpts which can be used to scan all keys.
var ScanAllKeys = ScanOpts{
Command: "SCAN",
}
type scanner struct {
Client
ScanOpts
res scanResult
resIdx int
err error
}
// NewScanner creates a new Scanner instance which will iterate over the redis
// instance's Client using the ScanOpts.
//
// NOTE if Client is a *Cluster this will not work correctly, use the NewScanner
// method on Cluster instead.
func NewScanner(c Client, o ScanOpts) Scanner {
return &scanner{
Client: c,
ScanOpts: o,
res: scanResult{
cur: "0",
},
}
}
func (s *scanner) Next(res *string) bool {
for {
if s.err != nil {
return false
}
for s.resIdx < len(s.res.keys) {
*res = s.res.keys[s.resIdx]
s.resIdx++
if *res != "" {
return true
}
}
if s.res.cur == "0" && s.res.keys != nil {
return false
}
s.err = s.Client.Do(s.cmd(&s.res, s.res.cur))
s.resIdx = 0
}
}
func (s *scanner) Close() error {
return s.err
}
type scanResult struct {
cur string
keys []string
}
func (s *scanResult) UnmarshalRESP(br *bufio.Reader) error {
var ah resp2.ArrayHeader
if err := ah.UnmarshalRESP(br); err != nil {
return err
} else if ah.N != 2 {
return errors.New("not enough parts returned")
}
var c resp2.BulkString
if err := c.UnmarshalRESP(br); err != nil {
return err
}
s.cur = c.S
s.keys = s.keys[:0]
return (resp2.Any{I: &s.keys}).UnmarshalRESP(br)
}