Skip to content

Commit da67568

Browse files
Add support for scanning Hashes
Extends redis_scan with new optional type field. Setting type to hash enables Hash type support. Without type field set falls back to previous string type processing.
1 parent 8706c1e commit da67568

File tree

2 files changed

+106
-16
lines changed

2 files changed

+106
-16
lines changed

docs/modules/components/pages/inputs/redis_scan.adoc

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
component_type_dropdown::[]
2424
2525
26-
Scans the set of keys in the current selected database and gets their values, using the Scan and Get commands.
26+
Scans the set of keys in the current selected database and gets their values, using the Scan and respective, typed, Get commands.
2727
2828
Introduced in version 4.27.0.
2929
@@ -42,6 +42,7 @@ input:
4242
url: redis://:6379 # No default (required)
4343
auto_replay_nacks: true
4444
match: ""
45+
type: string
4546
```
4647
4748
--
@@ -66,6 +67,7 @@ input:
6667
client_certs: []
6768
auto_replay_nacks: true
6869
match: ""
70+
type: string
6971
```
7072
7173
--
@@ -76,11 +78,26 @@ Optionally, iterates only elements matching a blob-style pattern. For example:
7678
- `*foo*` iterates only keys which contain `foo` in it.
7779
- `foo*` iterates only keys starting with `foo`.
7880
79-
This input generates a message for each key value pair in the following format:
81+
With no type specified (default) this input generates a message for each key value pair in the legacy format:
8082
8183
```json
8284
{"key":"foo","value":"bar"}
8385
```
86+
For `type` set to `string` this input generates a string for each key value pair in the following format:
87+
88+
```json
89+
"foovalue"
90+
```
91+
92+
The key is stored in the metadata field `key`.
93+
94+
For `type` set to `hash` this input generates a message for each key fields pair in the following format:
95+
96+
```json
97+
{"field1":"Hello","field2":"Hi"}
98+
```
99+
100+
The key is stored in the metadata field `key`.
84101
85102
86103
== Fields
@@ -335,4 +352,18 @@ match: foo
335352
match: '*4*'
336353
```
337354
355+
=== `type`
356+
357+
The type of the Redis keys to scan.
358+
359+
360+
*Type*: `string`
361+
362+
*Default*: `"string"`
363+
364+
Options:
365+
`hash`
366+
, `string`
367+
.
368+
338369

internal/impl/redis/input_scan.go

Lines changed: 73 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,39 @@ func init() {
3838
}
3939
}
4040

41-
const matchFieldName = "match"
41+
const (
42+
matchFieldName = "match"
43+
typeFieldName = "type"
44+
)
4245

4346
func redisScanInputConfig() *service.ConfigSpec {
4447
spec := service.NewConfigSpec().
45-
Summary(`Scans the set of keys in the current selected database and gets their values, using the Scan and Get commands.`).
48+
Summary(`Scans the set of keys in the current selected database and gets their values, using the Scan and respective, typed, Get commands.`).
4649
Description(`Optionally, iterates only elements matching a blob-style pattern. For example:
4750
4851
- ` + "`*foo*`" + ` iterates only keys which contain ` + "`foo`" + ` in it.
4952
- ` + "`foo*`" + ` iterates only keys starting with ` + "`foo`" + `.
5053
51-
This input generates a message for each key value pair in the following format:
54+
With no type specified (default) this input generates a message for each key value pair in the legacy format:
5255
5356
` + "```json" + `
5457
{"key":"foo","value":"bar"}
5558
` + "```" + `
59+
For ` + "`type`" + ` set to ` + "`string`" + ` this input generates a string for each key value pair in the following format:
60+
61+
` + "```json" + `
62+
"foovalue"
63+
` + "```" + `
64+
65+
The key is stored in the metadata field ` + "`key`" + `.
66+
67+
For ` + "`type`" + ` set to ` + "`hash`" + ` this input generates a message for each key fields pair in the following format:
68+
69+
` + "```json" + `
70+
{"field1":"Hello","field2":"Hi"}
71+
` + "```" + `
72+
73+
The key is stored in the metadata field ` + "`key`" + `.
5674
`).
5775
Categories("Services").
5876
Version("4.27.0")
@@ -70,7 +88,11 @@ This input generates a message for each key value pair in the following format:
7088
Example("foo*").
7189
Example("foo").
7290
Example("*4*").
73-
Default(""))
91+
Default("")).
92+
Field(service.NewStringEnumField(typeFieldName, "hash", "string").
93+
Description("The type of the Redis keys to scan.").
94+
Default("string").
95+
Optional())
7496
}
7597

7698
func newRedisScanInputFromConfig(conf *service.ParsedConfig, mgr *service.Resources) (service.Input, error) {
@@ -82,10 +104,17 @@ func newRedisScanInputFromConfig(conf *service.ParsedConfig, mgr *service.Resour
82104
if err != nil {
83105
return nil, fmt.Errorf("error retrieving %s: %v", matchFieldName, err)
84106
}
107+
108+
t, err := conf.FieldString(typeFieldName)
109+
if err != nil {
110+
return nil, fmt.Errorf("error retrieving %s: %v", typeFieldName, err)
111+
}
112+
85113
r := &redisScanReader{
86114
client: client,
87115
match: match,
88116
log: mgr.Logger(),
117+
t: t,
89118
}
90119
return r, nil
91120
}
@@ -95,31 +124,61 @@ type redisScanReader struct {
95124
client redis.UniversalClient
96125
iter *redis.ScanIterator
97126
log *service.Logger
127+
t string
98128
}
99129

100130
func (r *redisScanReader) Connect(ctx context.Context) error {
101131
_, err := r.client.Ping(ctx).Result()
102132
if err != nil {
103133
return err
104134
}
105-
r.iter = r.client.Scan(context.Background(), 0, r.match, 0).Iterator()
135+
if r.t == "" {
136+
r.iter = r.client.Scan(context.Background(), 0, r.match, 0).Iterator()
137+
} else {
138+
r.iter = r.client.ScanType(context.Background(), 0, r.match, 0, r.t).Iterator()
139+
}
106140
return r.iter.Err()
107141
}
108142

109143
func (r *redisScanReader) Read(ctx context.Context) (*service.Message, service.AckFunc, error) {
110144
if r.iter.Next(ctx) {
111145
key := r.iter.Val()
112146

113-
res := r.client.Get(ctx, key)
114-
if err := res.Err(); err != nil {
115-
return nil, nil, err
116-
}
117-
118147
msg := service.NewMessage(nil)
119-
msg.SetStructuredMut(map[string]any{
120-
"key": key,
121-
"value": res.Val(),
122-
})
148+
switch r.t {
149+
case "hash":
150+
res := r.client.HGetAll(ctx, key)
151+
if err := res.Err(); err != nil {
152+
return nil, nil, err
153+
}
154+
155+
m := make(map[string]any, len(res.Val()))
156+
for k, v := range res.Val() {
157+
m[k] = v
158+
}
159+
160+
msg.SetStructuredMut(m)
161+
msg.MetaSet("key", key)
162+
case "string":
163+
res := r.client.Get(ctx, key)
164+
if err := res.Err(); err != nil {
165+
return nil, nil, err
166+
}
167+
168+
msg.SetStructuredMut(res.Val())
169+
msg.MetaSet("key", key)
170+
default:
171+
// legacy behavior
172+
res := r.client.Get(ctx, key)
173+
if err := res.Err(); err != nil {
174+
return nil, nil, err
175+
}
176+
177+
msg.SetStructuredMut(map[string]any{
178+
"key": key,
179+
"value": res.Val(),
180+
})
181+
}
123182
return msg, func(ctx context.Context, err error) error {
124183
return err
125184
}, nil

0 commit comments

Comments
 (0)