mirror of
https://github.com/redis/go-redis.git
synced 2026-10-28 08:24:54 +08:00
Use iter.Seq to iterate over ScanIterator (#3348)
* Add support for iter.Seq for ScanIterator * Simplify Vals * Review fixes
This commit is contained in:
1 parent
3f37a3a4e4
commit
ad6e893f5f
4 files changed
+146
-14
No files matched your search
@@ -28,8 +28,8 @@ func main() {
|
||||
checker.Start(ctx)
|
||||
|
||||
iter := rdb.Scan(ctx, 0, "", 0).Iterator()
|
||||
for iter.Next(ctx) {
|
||||
checker.Add(iter.Val())
|
||||
for val := range iter.Vals(ctx) {
|
||||
checker.Add(val)
|
||||
}
|
||||
if err := iter.Err(); err != nil {
|
||||
panic(err)
|
||||
|
||||
+4
-4
@@ -632,8 +632,8 @@ func Example_customCommand2() {
|
||||
|
||||
func ExampleScanIterator() {
|
||||
iter := rdb.Scan(ctx, 0, "", 0).Iterator()
|
||||
for iter.Next(ctx) {
|
||||
fmt.Println(iter.Val())
|
||||
for val := range iter.Vals(ctx) {
|
||||
fmt.Println(val)
|
||||
}
|
||||
if err := iter.Err(); err != nil {
|
||||
panic(err)
|
||||
@@ -642,8 +642,8 @@ func ExampleScanIterator() {
|
||||
|
||||
func ExampleScanCmd_Iterator() {
|
||||
iter := rdb.Scan(ctx, 0, "", 0).Iterator()
|
||||
for iter.Next(ctx) {
|
||||
fmt.Println(iter.Val())
|
||||
for val := range iter.Vals(ctx) {
|
||||
fmt.Println(val)
|
||||
}
|
||||
if err := iter.Err(); err != nil {
|
||||
panic(err)
|
||||
|
||||
+51
@@ -2,6 +2,7 @@ package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"iter"
|
||||
)
|
||||
|
||||
// ScanIterator is used to incrementally iterate over a collection of elements.
|
||||
@@ -16,6 +17,8 @@ func (it *ScanIterator) Err() error {
|
||||
}
|
||||
|
||||
// Next advances the cursor and returns true if more values can be read.
|
||||
//
|
||||
// Deprecated: support for native iterators has been added in go 1.23. Use Vals instead.
|
||||
func (it *ScanIterator) Next(ctx context.Context) bool {
|
||||
// Instantly return on errors.
|
||||
if it.cmd.Err() != nil {
|
||||
@@ -65,6 +68,8 @@ func (it *ScanIterator) Next(ctx context.Context) bool {
|
||||
}
|
||||
|
||||
// Val returns the key/field at the current cursor position.
|
||||
//
|
||||
// Deprecated: support for native iterators has been added in go 1.23. Use Vals instead.
|
||||
func (it *ScanIterator) Val() string {
|
||||
var v string
|
||||
if it.cmd.Err() == nil && it.pos > 0 && it.pos <= len(it.cmd.page) {
|
||||
@@ -72,3 +77,49 @@ func (it *ScanIterator) Val() string {
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Vals returns iterator over key/field at the current cursor position.
|
||||
func (it *ScanIterator) Vals(ctx context.Context) iter.Seq[string] {
|
||||
return func(yield func(string) bool) {
|
||||
if it.cmd.Err() != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
for _, val := range it.cmd.page {
|
||||
if !yield(val) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Return if there is no more data to fetch.
|
||||
if it.cmd.cursor == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch next page.
|
||||
var cursorIndex int
|
||||
switch it.cmd.args[0] {
|
||||
case "scan", "qscan":
|
||||
cursorIndex = 1
|
||||
default:
|
||||
cursorIndex = 2
|
||||
}
|
||||
|
||||
it.cmd.args[cursorIndex] = it.cmd.cursor
|
||||
|
||||
err := it.cmd.process(ctx, it.cmd)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Await the fetch before reading page/cursor: on the deferred
|
||||
// autopipeline face process() only enqueues, and reading the raw
|
||||
// fields of a not-yet-executed command would spin re-issuing SCANs
|
||||
// with a stale cursor forever. Err() blocks until executed there and
|
||||
// is a no-op read everywhere else.
|
||||
if it.cmd.Err() != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+89
-8
@@ -2,6 +2,7 @@ package redis_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
. "github.com/bsm/ginkgo/v2"
|
||||
. "github.com/bsm/gomega"
|
||||
@@ -55,13 +56,20 @@ var _ = Describe("ScanIterator", func() {
|
||||
Expect(closeSubject()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should scan across empty DBs", func() {
|
||||
It("should scan across empty DBs using Next", func() {
|
||||
iter := client.Scan(ctx, 0, "", 10).Iterator()
|
||||
Expect(iter.Next(ctx)).To(BeFalse())
|
||||
Expect(iter.Err()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should scan across one page", func() {
|
||||
It("should scan across empty DBs using Vals", func() {
|
||||
iter := client.Scan(ctx, 0, "", 10).Iterator()
|
||||
vals := slices.Collect(iter.Vals(ctx))
|
||||
Expect(vals).To(BeEmpty())
|
||||
Expect(iter.Err()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should scan across one page using Next", func() {
|
||||
Expect(seed(7)).NotTo(HaveOccurred())
|
||||
|
||||
var vals []string
|
||||
@@ -73,7 +81,16 @@ var _ = Describe("ScanIterator", func() {
|
||||
Expect(vals).To(ConsistOf([]string{"K01", "K02", "K03", "K04", "K05", "K06", "K07"}))
|
||||
})
|
||||
|
||||
It("should scan across multiple pages", func() {
|
||||
It("should scan across one page using Vals", func() {
|
||||
Expect(seed(7)).NotTo(HaveOccurred())
|
||||
|
||||
iter := client.Scan(ctx, 0, "", 0).Iterator()
|
||||
vals := slices.Collect(iter.Vals(ctx))
|
||||
Expect(iter.Err()).NotTo(HaveOccurred())
|
||||
Expect(vals).To(ConsistOf([]string{"K01", "K02", "K03", "K04", "K05", "K06", "K07"}))
|
||||
})
|
||||
|
||||
It("should scan across multiple pages using Next", func() {
|
||||
Expect(seed(71)).NotTo(HaveOccurred())
|
||||
|
||||
var vals []string
|
||||
@@ -87,7 +104,18 @@ var _ = Describe("ScanIterator", func() {
|
||||
Expect(vals).To(ContainElement("K71"))
|
||||
})
|
||||
|
||||
It("should hscan across multiple pages", func() {
|
||||
It("should scan across multiple pages using Vals", func() {
|
||||
Expect(seed(71)).NotTo(HaveOccurred())
|
||||
|
||||
iter := client.Scan(ctx, 0, "", 10).Iterator()
|
||||
vals := slices.Collect(iter.Vals(ctx))
|
||||
Expect(iter.Err()).NotTo(HaveOccurred())
|
||||
Expect(vals).To(HaveLen(71))
|
||||
Expect(vals).To(ContainElement("K01"))
|
||||
Expect(vals).To(ContainElement("K71"))
|
||||
})
|
||||
|
||||
It("should hscan across multiple pages using Next", func() {
|
||||
SkipBeforeRedisVersion("7.4", "doesn't work with older redis stack images")
|
||||
Expect(hashSeed(71)).NotTo(HaveOccurred())
|
||||
|
||||
@@ -103,7 +131,20 @@ var _ = Describe("ScanIterator", func() {
|
||||
Expect(vals).To(ContainElement("x"))
|
||||
})
|
||||
|
||||
It("should hscan without values across multiple pages", Label("NonRedisEnterprise"), func() {
|
||||
It("should hscan across multiple pages using Vals", func() {
|
||||
SkipBeforeRedisVersion("7.4", "doesn't work with older redis stack images")
|
||||
Expect(hashSeed(71)).NotTo(HaveOccurred())
|
||||
|
||||
iter := client.HScan(ctx, hashKey, 0, "", 10).Iterator()
|
||||
vals := slices.Collect(iter.Vals(ctx))
|
||||
Expect(iter.Err()).NotTo(HaveOccurred())
|
||||
Expect(vals).To(HaveLen(71 * 2))
|
||||
Expect(vals).To(ContainElement("K01"))
|
||||
Expect(vals).To(ContainElement("K71"))
|
||||
Expect(vals).To(ContainElement("x"))
|
||||
})
|
||||
|
||||
It("should hscan without values across multiple pages using Next", Label("NonRedisEnterprise"), func() {
|
||||
SkipBeforeRedisVersion("7.4", "doesn't work with older redis stack images")
|
||||
Expect(hashSeed(71)).NotTo(HaveOccurred())
|
||||
|
||||
@@ -119,7 +160,20 @@ var _ = Describe("ScanIterator", func() {
|
||||
Expect(vals).NotTo(ContainElement("x"))
|
||||
})
|
||||
|
||||
It("should scan to page borders", func() {
|
||||
It("should hscan without values across multiple pages using Vals", Label("NonRedisEnterprise"), func() {
|
||||
SkipBeforeRedisVersion("7.4", "doesn't work with older redis stack images")
|
||||
Expect(hashSeed(71)).NotTo(HaveOccurred())
|
||||
|
||||
iter := client.HScanNoValues(ctx, hashKey, 0, "", 10).Iterator()
|
||||
vals := slices.Collect(iter.Vals(ctx))
|
||||
Expect(iter.Err()).NotTo(HaveOccurred())
|
||||
Expect(vals).To(HaveLen(71))
|
||||
Expect(vals).To(ContainElement("K01"))
|
||||
Expect(vals).To(ContainElement("K71"))
|
||||
Expect(vals).NotTo(ContainElement("x"))
|
||||
})
|
||||
|
||||
It("should scan to page borders using Next", func() {
|
||||
Expect(seed(20)).NotTo(HaveOccurred())
|
||||
|
||||
var vals []string
|
||||
@@ -131,7 +185,16 @@ var _ = Describe("ScanIterator", func() {
|
||||
Expect(vals).To(HaveLen(20))
|
||||
})
|
||||
|
||||
It("should scan with match", func() {
|
||||
It("should scan to page borders using Vals", func() {
|
||||
Expect(seed(20)).NotTo(HaveOccurred())
|
||||
|
||||
iter := client.Scan(ctx, 0, "", 10).Iterator()
|
||||
vals := slices.Collect(iter.Vals(ctx))
|
||||
Expect(iter.Err()).NotTo(HaveOccurred())
|
||||
Expect(vals).To(HaveLen(20))
|
||||
})
|
||||
|
||||
It("should scan with match using Next", func() {
|
||||
Expect(seed(33)).NotTo(HaveOccurred())
|
||||
|
||||
var vals []string
|
||||
@@ -143,7 +206,16 @@ var _ = Describe("ScanIterator", func() {
|
||||
Expect(vals).To(HaveLen(13))
|
||||
})
|
||||
|
||||
It("should scan with match across empty pages", func() {
|
||||
It("should scan with match using Vals", func() {
|
||||
Expect(seed(33)).NotTo(HaveOccurred())
|
||||
|
||||
iter := client.Scan(ctx, 0, "K*2*", 10).Iterator()
|
||||
vals := slices.Collect(iter.Vals(ctx))
|
||||
Expect(iter.Err()).NotTo(HaveOccurred())
|
||||
Expect(vals).To(HaveLen(13))
|
||||
})
|
||||
|
||||
It("should scan with match across empty pages using Next", func() {
|
||||
Expect(extraSeed(2, 10)).NotTo(HaveOccurred())
|
||||
|
||||
var vals []string
|
||||
@@ -154,4 +226,13 @@ var _ = Describe("ScanIterator", func() {
|
||||
Expect(iter.Err()).NotTo(HaveOccurred())
|
||||
Expect(vals).To(HaveLen(2))
|
||||
})
|
||||
|
||||
It("should scan with match across empty pages using Vals", func() {
|
||||
Expect(extraSeed(2, 10)).NotTo(HaveOccurred())
|
||||
|
||||
iter := client.Scan(ctx, 0, "K*", 1).Iterator()
|
||||
vals := slices.Collect(iter.Vals(ctx))
|
||||
Expect(iter.Err()).NotTo(HaveOccurred())
|
||||
Expect(vals).To(HaveLen(2))
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user