Repository: redis/go-redis

Issue:
Add support for HSCAN [NOVALUES]
As documented in this [pull request](https://github.com/redis/redis/pull/12765)

Discussion:

Solution patch:
diff --git a/hash_commands.go b/hash_commands.go
index 59fbb86..ef69064 100644
--- a/hash_commands.go
+++ b/hash_commands.go
@@ -19,6 +19,7 @@ type HashCmdable interface {
 	HMSet(ctx context.Context, key string, values ...interface{}) *BoolCmd
 	HSetNX(ctx context.Context, key, field string, value interface{}) *BoolCmd
 	HScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd
+	HScanNoValues(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd
 	HVals(ctx context.Context, key string) *StringSliceCmd
 	HRandField(ctx context.Context, key string, count int) *StringSliceCmd
 	HRandFieldWithValues(ctx context.Context, key string, count int) *KeyValueSliceCmd
@@ -176,6 +177,20 @@ func (c cmdable) HScan(ctx context.Context, key string, cursor uint64, match str
 	return cmd
 }
 
+func (c cmdable) HScanNoValues(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd {
+	args := []interface{}{"hscan", key, cursor}
+	if match != "" {
+		args = append(args, "match", match)
+	}
+	if count > 0 {
+		args = append(args, "count", count)
+	}
+	args = append(args, "novalues")
+	cmd := NewScanCmd(ctx, c, args...)
+	_ = c(ctx, cmd)
+	return cmd
+}
+
 type HExpireArgs struct {
 	NX bool
 	XX bool



Test patch:
diff --git a/commands_test.go b/commands_test.go
index 5d78804..dea3d5f 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -1134,8 +1134,26 @@ var _ = Describe("Commands", func() {
 
 			keys, cursor, err := client.HScan(ctx, "myhash", 0, "", 0).Result()
 			Expect(err).NotTo(HaveOccurred())
-			Expect(keys).NotTo(BeEmpty())
-			Expect(cursor).NotTo(BeZero())
+			// If we don't get at least two items back, it's really strange.
+			Expect(cursor).To(BeNumerically(">=", 2))
+			Expect(len(keys)).To(BeNumerically(">=", 2))
+			Expect(keys[0]).To(HavePrefix("key"))
+			Expect(keys[1]).To(Equal("hello"))
+		})
+
+		It("should HScan without values", func() {
+			for i := 0; i < 1000; i++ {
+				sadd := client.HSet(ctx, "myhash", fmt.Sprintf("key%d", i), "hello")
+				Expect(sadd.Err()).NotTo(HaveOccurred())
+			}
+
+			keys, cursor, err := client.HScanNoValues(ctx, "myhash", 0, "", 0).Result()
+			Expect(err).NotTo(HaveOccurred())
+			// If we don't get at least two items back, it's really strange.
+			Expect(cursor).To(BeNumerically(">=", 2))
+			Expect(len(keys)).To(BeNumerically(">=", 2))
+			Expect(keys[0]).To(HavePrefix("key"))
+			Expect(keys[1]).To(HavePrefix("key"))
 		})
 
 		It("should ZScan", func() {
diff --git a/iterator_test.go b/iterator_test.go
index ccd9414..dfa8fbb 100644
--- a/iterator_test.go
+++ b/iterator_test.go
@@ -96,6 +96,22 @@ var _ = Describe("ScanIterator", func() {
 		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", func() {
+		Expect(hashSeed(71)).NotTo(HaveOccurred())
+
+		var vals []string
+		iter := client.HScanNoValues(ctx, hashKey, 0, "", 10).Iterator()
+		for iter.Next(ctx) {
+			vals = append(vals, iter.Val())
+		}
+		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", func() {