diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md
index bf057512f..02570d3e1 100644
--- a/docs/ReleaseNotes.md
+++ b/docs/ReleaseNotes.md
@@ -9,6 +9,7 @@ Current package versions:
## Unreleased
- Add support for hash field expiration (see [#2715](https://github.com/StackExchange/StackExchange.Redis/issues/2715)) ([#2716 by atakavci](https://github.com/StackExchange/StackExchange.Redis/pull/2716]))
+- Add support for `HSCAN NOVALUES` (see [#2721](https://github.com/StackExchange/StackExchange.Redis/issues/2721)) ([#2722 by atakavci](https://github.com/StackExchange/StackExchange.Redis/pull/2722))
## 2.8.0
diff --git a/src/StackExchange.Redis/Interfaces/IDatabase.cs b/src/StackExchange.Redis/Interfaces/IDatabase.cs
index c192f07f9..207c03326 100644
--- a/src/StackExchange.Redis/Interfaces/IDatabase.cs
+++ b/src/StackExchange.Redis/Interfaces/IDatabase.cs
@@ -628,6 +628,20 @@ public interface IDatabase : IRedis, IDatabaseAsync
///
IEnumerable HashScan(RedisKey key, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None);
+ ///
+ /// The HSCAN command is used to incrementally iterate over a hash and return only field names.
+ /// Note: to resume an iteration via cursor, cast the original enumerable or enumerator to .
+ ///
+ /// The key of the hash.
+ /// The pattern of keys to get entries for.
+ /// The page size to iterate by.
+ /// The cursor position to start at.
+ /// The page offset to start at.
+ /// The flags to use for this operation.
+ /// Yields all elements of the hash matching the pattern.
+ ///
+ IEnumerable HashScanNoValues(RedisKey key, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None);
+
///
/// Sets the specified fields to their respective values in the hash stored at key.
/// This command overwrites any specified fields that already exist in the hash, leaving other unspecified fields untouched.
diff --git a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
index 8600a5a1a..9852c131c 100644
--- a/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
+++ b/src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
@@ -135,6 +135,9 @@ public interface IDatabaseAsync : IRedisAsync
///
IAsyncEnumerable HashScanAsync(RedisKey key, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None);
+ ///
+ IAsyncEnumerable HashScanNoValuesAsync(RedisKey key, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None);
+
///
Task HashSetAsync(RedisKey key, HashEntry[] hashFields, CommandFlags flags = CommandFlags.None);
diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs
index f18e74512..b97bba73b 100644
--- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs
+++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs
@@ -135,6 +135,9 @@ public Task HashRandomFieldsWithValuesAsync(RedisKey key, long coun
public IAsyncEnumerable HashScanAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) =>
Inner.HashScanAsync(ToInner(key), pattern, pageSize, cursor, pageOffset, flags);
+ public IAsyncEnumerable HashScanNoValuesAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) =>
+ Inner.HashScanNoValuesAsync(ToInner(key), pattern, pageSize, cursor, pageOffset, flags);
+
public Task HashSetAsync(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) =>
Inner.HashSetAsync(ToInner(key), hashField, value, when, flags);
diff --git a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs
index 8f570edd6..75d93d0f9 100644
--- a/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs
+++ b/src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs
@@ -721,6 +721,9 @@ IEnumerable IDatabase.HashScan(RedisKey key, RedisValue pattern, int
IEnumerable IDatabase.HashScan(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags)
=> Inner.HashScan(ToInner(key), pattern, pageSize, cursor, pageOffset, flags);
+ IEnumerable IDatabase.HashScanNoValues(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags)
+ => Inner.HashScanNoValues(ToInner(key), pattern, pageSize, cursor, pageOffset, flags);
+
IEnumerable IDatabase.SetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags)
=> Inner.SetScan(ToInner(key), pattern, pageSize, flags);
diff --git a/src/StackExchange.Redis/Message.cs b/src/StackExchange.Redis/Message.cs
index 876f718c2..b89a6b946 100644
--- a/src/StackExchange.Redis/Message.cs
+++ b/src/StackExchange.Redis/Message.cs
@@ -292,6 +292,9 @@ public static Message Create(int db, CommandFlags flags, RedisCommand command, i
public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4) =>
new CommandKeyValueValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3, value4);
+ public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4, in RedisValue value5) =>
+ new CommandKeyValueValueValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3, value4, value5);
+
public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4, in RedisValue value5, in RedisValue value6) =>
new CommandKeyValueValueValueValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3, value4, value5, value6);
@@ -1276,6 +1279,40 @@ protected override void WriteImpl(PhysicalConnection physical)
public override int ArgCount => 6;
}
+ private sealed class CommandKeyValueValueValueValueValueValueMessage : CommandKeyBase
+ {
+ private readonly RedisValue value0, value1, value2, value3, value4, value5;
+
+ public CommandKeyValueValueValueValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4, in RedisValue value5) : base(db, flags, command, key)
+ {
+ value0.AssertNotNull();
+ value1.AssertNotNull();
+ value2.AssertNotNull();
+ value3.AssertNotNull();
+ value4.AssertNotNull();
+ value5.AssertNotNull();
+ this.value0 = value0;
+ this.value1 = value1;
+ this.value2 = value2;
+ this.value3 = value3;
+ this.value4 = value4;
+ this.value5 = value5;
+ }
+
+ protected override void WriteImpl(PhysicalConnection physical)
+ {
+ physical.WriteHeader(Command, ArgCount);
+ physical.Write(Key);
+ physical.WriteBulkString(value0);
+ physical.WriteBulkString(value1);
+ physical.WriteBulkString(value2);
+ physical.WriteBulkString(value3);
+ physical.WriteBulkString(value4);
+ physical.WriteBulkString(value5);
+ }
+ public override int ArgCount => 7;
+ }
+
private sealed class CommandKeyValueValueValueValueValueValueValueMessage : CommandKeyBase
{
private readonly RedisValue value0, value1, value2, value3, value4, value5, value6;
diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
index 7b0a515df..a24333c8e 100644
--- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
+++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
@@ -581,6 +581,7 @@ StackExchange.Redis.IDatabase.HashRandomFields(StackExchange.Redis.RedisKey key,
StackExchange.Redis.IDatabase.HashRandomFieldsWithValues(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.HashEntry[]!
StackExchange.Redis.IDatabase.HashScan(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pattern = default(StackExchange.Redis.RedisValue), int pageSize = 250, long cursor = 0, int pageOffset = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Collections.Generic.IEnumerable!
StackExchange.Redis.IDatabase.HashScan(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pattern, int pageSize, StackExchange.Redis.CommandFlags flags) -> System.Collections.Generic.IEnumerable!
+StackExchange.Redis.IDatabase.HashScanNoValues(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pattern = default(StackExchange.Redis.RedisValue), int pageSize = 250, long cursor = 0, int pageOffset = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Collections.Generic.IEnumerable!
StackExchange.Redis.IDatabase.HashSet(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void
StackExchange.Redis.IDatabase.HashSet(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.RedisValue value, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> bool
StackExchange.Redis.IDatabase.HashStringLength(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
@@ -818,6 +819,7 @@ StackExchange.Redis.IDatabaseAsync.HashRandomFieldAsync(StackExchange.Redis.Redi
StackExchange.Redis.IDatabaseAsync.HashRandomFieldsAsync(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
StackExchange.Redis.IDatabaseAsync.HashRandomFieldsWithValuesAsync(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
StackExchange.Redis.IDatabaseAsync.HashScanAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pattern = default(StackExchange.Redis.RedisValue), int pageSize = 250, long cursor = 0, int pageOffset = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Collections.Generic.IAsyncEnumerable!
+StackExchange.Redis.IDatabaseAsync.HashScanNoValuesAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pattern = default(StackExchange.Redis.RedisValue), int pageSize = 250, long cursor = 0, int pageOffset = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Collections.Generic.IAsyncEnumerable!
StackExchange.Redis.IDatabaseAsync.HashSetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
StackExchange.Redis.IDatabaseAsync.HashSetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.RedisValue value, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
StackExchange.Redis.IDatabaseAsync.HashStringLengthAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs
index 0e5eada53..7468bdb64 100644
--- a/src/StackExchange.Redis/RedisDatabase.cs
+++ b/src/StackExchange.Redis/RedisDatabase.cs
@@ -624,6 +624,23 @@ private CursorEnumerable HashScanAsync(RedisKey key, RedisValue patte
throw ExceptionFactory.NotSupported(true, RedisCommand.HSCAN);
}
+ IEnumerable IDatabase.HashScanNoValues(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags)
+ => HashScanNoValuesAsync(key, pattern, pageSize, cursor, pageOffset, flags);
+
+ IAsyncEnumerable IDatabaseAsync.HashScanNoValuesAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags)
+ => HashScanNoValuesAsync(key, pattern, pageSize, cursor, pageOffset, flags);
+
+ private CursorEnumerable HashScanNoValuesAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags)
+ {
+ var scan = TryScan(key, pattern, pageSize, cursor, pageOffset, flags, RedisCommand.HSCAN, SetScanResultProcessor.Default, out var server, true);
+ if (scan != null) return scan;
+
+ if (cursor != 0) throw ExceptionFactory.NoCursor(RedisCommand.HKEYS);
+
+ if (pattern.IsNull) return CursorEnumerable.From(this, server, HashKeysAsync(key, flags), pageOffset);
+ throw ExceptionFactory.NotSupported(true, RedisCommand.HSCAN);
+ }
+
public bool HashSet(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None)
{
WhenAlwaysOrNotExists(when);
@@ -4679,7 +4696,7 @@ private Message GetStringSetAndGetMessage(
_ => throw new ArgumentOutOfRangeException(nameof(operation)),
};
- private CursorEnumerable? TryScan(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags, RedisCommand command, ResultProcessor.ScanResult> processor, out ServerEndPoint? server)
+ private CursorEnumerable? TryScan(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags, RedisCommand command, ResultProcessor.ScanResult> processor, out ServerEndPoint? server, bool noValues = false)
{
server = null;
if (pageSize <= 0)
@@ -4690,7 +4707,7 @@ private Message GetStringSetAndGetMessage(
if (!features.Scan) return null;
if (CursorUtils.IsNil(pattern)) pattern = (byte[]?)null;
- return new ScanEnumerable(this, server, key, pattern, pageSize, cursor, pageOffset, flags, command, processor);
+ return new ScanEnumerable(this, server, key, pattern, pageSize, cursor, pageOffset, flags, command, processor, noValues);
}
private Message GetLexMessage(RedisCommand command, RedisKey key, RedisValue min, RedisValue max, Exclude exclude, long skip, long take, CommandFlags flags)
@@ -4783,6 +4800,7 @@ internal class ScanEnumerable : CursorEnumerable
private readonly RedisKey key;
private readonly RedisValue pattern;
private readonly RedisCommand command;
+ private readonly bool noValues;
public ScanEnumerable(
RedisDatabase database,
@@ -4794,19 +4812,28 @@ public ScanEnumerable(
int pageOffset,
CommandFlags flags,
RedisCommand command,
- ResultProcessor processor)
+ ResultProcessor processor,
+ bool noValues)
: base(database, server, database.Database, pageSize, cursor, pageOffset, flags)
{
this.key = key;
this.pattern = pattern;
this.command = command;
Processor = processor;
+ this.noValues = noValues;
}
private protected override ResultProcessor.ScanResult> Processor { get; }
private protected override Message CreateMessage(in RedisValue cursor)
{
+ if (noValues)
+ {
+ if (CursorUtils.IsNil(pattern) && pageSize == CursorUtils.DefaultRedisPageSize) return Message.Create(db, flags, command, key, cursor, RedisLiterals.NOVALUES);
+ if (CursorUtils.IsNil(pattern)) return Message.Create(db, flags, command, key, cursor, RedisLiterals.COUNT, pageSize, RedisLiterals.NOVALUES);
+ return Message.Create(db, flags, command, key, cursor, RedisLiterals.MATCH, pattern, RedisLiterals.COUNT, pageSize, RedisLiterals.NOVALUES);
+ }
+
if (CursorUtils.IsNil(pattern))
{
if (pageSize == CursorUtils.DefaultRedisPageSize)
@@ -4826,7 +4853,7 @@ private protected override Message CreateMessage(in RedisValue cursor)
}
else
{
- return Message.Create(db, flags, command, key, new RedisValue[] { cursor, RedisLiterals.MATCH, pattern, RedisLiterals.COUNT, pageSize });
+ return Message.Create(db, flags, command, key, cursor, RedisLiterals.MATCH, pattern, RedisLiterals.COUNT, pageSize);
}
}
}
diff --git a/src/StackExchange.Redis/RedisLiterals.cs b/src/StackExchange.Redis/RedisLiterals.cs
index a076ff3fe..549691fd2 100644
--- a/src/StackExchange.Redis/RedisLiterals.cs
+++ b/src/StackExchange.Redis/RedisLiterals.cs
@@ -113,6 +113,7 @@ public static readonly RedisValue
NODES = "NODES",
NOSAVE = "NOSAVE",
NOT = "NOT",
+ NOVALUES = "NOVALUES",
NUMPAT = "NUMPAT",
NUMSUB = "NUMSUB",
NX = "NX",
diff --git a/tests/StackExchange.Redis.Tests/HashTests.cs b/tests/StackExchange.Redis.Tests/HashTests.cs
index 31f3fa9f6..b949f5911 100644
--- a/tests/StackExchange.Redis.Tests/HashTests.cs
+++ b/tests/StackExchange.Redis.Tests/HashTests.cs
@@ -126,6 +126,91 @@ public void Scan()
Assert.Equal("ghi=jkl", string.Join(",", v4.Select(pair => pair.Name + "=" + pair.Value)));
}
+ [Fact]
+ public async Task ScanNoValuesAsync()
+ {
+ using var conn = Create(require: RedisFeatures.v7_4_0_rc1);
+
+ var db = conn.GetDatabase();
+ var key = Me();
+ await db.KeyDeleteAsync(key);
+ for (int i = 0; i < 200; i++)
+ {
+ await db.HashSetAsync(key, "key" + i, "value " + i);
+ }
+
+ int count = 0;
+ // works for async
+ await foreach (var _ in db.HashScanNoValuesAsync(key, pageSize: 20))
+ {
+ count++;
+ }
+ Assert.Equal(200, count);
+
+ // and sync=>async (via cast)
+ count = 0;
+ await foreach (var _ in (IAsyncEnumerable)db.HashScanNoValues(key, pageSize: 20))
+ {
+ count++;
+ }
+ Assert.Equal(200, count);
+
+ // and sync (native)
+ count = 0;
+ foreach (var _ in db.HashScanNoValues(key, pageSize: 20))
+ {
+ count++;
+ }
+ Assert.Equal(200, count);
+
+ // and async=>sync (via cast)
+ count = 0;
+ foreach (var _ in (IEnumerable)db.HashScanNoValuesAsync(key, pageSize: 20))
+ {
+ count++;
+ }
+ Assert.Equal(200, count);
+ }
+
+ [Fact]
+ public void ScanNoValues()
+ {
+ using var conn = Create(require: RedisFeatures.v7_4_0_rc1);
+
+ var db = conn.GetDatabase();
+
+ var key = Me();
+ db.KeyDeleteAsync(key);
+ db.HashSetAsync(key, "abc", "def");
+ db.HashSetAsync(key, "ghi", "jkl");
+ db.HashSetAsync(key, "mno", "pqr");
+
+ var t1 = db.HashScanNoValues(key);
+ var t2 = db.HashScanNoValues(key, "*h*");
+ var t3 = db.HashScanNoValues(key);
+ var t4 = db.HashScanNoValues(key, "*h*");
+
+ var v1 = t1.ToArray();
+ var v2 = t2.ToArray();
+ var v3 = t3.ToArray();
+ var v4 = t4.ToArray();
+
+ Assert.Equal(3, v1.Length);
+ Assert.Single(v2);
+ Assert.Equal(3, v3.Length);
+ Assert.Single(v4);
+
+ Array.Sort(v1);
+ Array.Sort(v2);
+ Array.Sort(v3);
+ Array.Sort(v4);
+
+ Assert.Equal(new RedisValue[] { "abc", "ghi", "mno" }, v1);
+ Assert.Equal(new RedisValue[] { "ghi" }, v2);
+ Assert.Equal(new RedisValue[] { "abc", "ghi", "mno" }, v3);
+ Assert.Equal(new RedisValue[] { "ghi" }, v4);
+ }
+
[Fact]
public void TestIncrementOnHashThatDoesntExist()
{
diff --git a/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs b/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs
index 587d5a0da..f467aca24 100644
--- a/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs
+++ b/tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs
@@ -162,6 +162,20 @@ public void HashScan_Full()
mock.Received().HashScan("prefix:key", "pattern", 123, 42, 64, CommandFlags.None);
}
+ [Fact]
+ public void HashScanNoValues()
+ {
+ prefixed.HashScanNoValues("key", "pattern", 123, flags: CommandFlags.None);
+ mock.Received().HashScanNoValues("prefix:key", "pattern", 123, flags: CommandFlags.None);
+ }
+
+ [Fact]
+ public void HashScanNoValues_Full()
+ {
+ prefixed.HashScanNoValues("key", "pattern", 123, 42, 64, flags: CommandFlags.None);
+ mock.Received().HashScanNoValues("prefix:key", "pattern", 123, 42, 64, CommandFlags.None);
+ }
+
[Fact]
public void HashSet_1()
{
diff --git a/tests/StackExchange.Redis.Tests/NamingTests.cs b/tests/StackExchange.Redis.Tests/NamingTests.cs
index 3f34ada64..2990e04c4 100644
--- a/tests/StackExchange.Redis.Tests/NamingTests.cs
+++ b/tests/StackExchange.Redis.Tests/NamingTests.cs
@@ -115,6 +115,7 @@ private static bool IgnoreMethodConventions(MethodInfo method)
case nameof(IDatabase.SetScan):
case nameof(IDatabase.SortedSetScan):
case nameof(IDatabase.HashScan):
+ case nameof(IDatabase.HashScanNoValues):
case nameof(ISubscriber.SubscribedEndpoint):
return true;
}
diff --git a/tests/StackExchange.Redis.Tests/ScanTests.cs b/tests/StackExchange.Redis.Tests/ScanTests.cs
index 4524aed9f..a95435e4d 100644
--- a/tests/StackExchange.Redis.Tests/ScanTests.cs
+++ b/tests/StackExchange.Redis.Tests/ScanTests.cs
@@ -332,6 +332,58 @@ public void HashScanLarge(int pageSize)
Assert.Equal(2000, count);
}
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void HashScanNoValues(bool supported)
+ {
+ string[]? disabledCommands = supported ? null : new[] { "hscan" };
+
+ using var conn = Create(require: RedisFeatures.v7_4_0_rc1, disabledCommands: disabledCommands);
+
+ RedisKey key = Me();
+ var db = conn.GetDatabase();
+ db.KeyDelete(key, CommandFlags.FireAndForget);
+
+ db.HashSet(key, "a", "1", flags: CommandFlags.FireAndForget);
+ db.HashSet(key, "b", "2", flags: CommandFlags.FireAndForget);
+ db.HashSet(key, "c", "3", flags: CommandFlags.FireAndForget);
+
+ var arr = db.HashScanNoValues(key).ToArray();
+ Assert.Equal(3, arr.Length);
+ Assert.True(arr.Any(x => x == "a"), "a");
+ Assert.True(arr.Any(x => x == "b"), "b");
+ Assert.True(arr.Any(x => x == "c"), "c");
+
+ var basic = db.HashGetAll(key).ToDictionary();
+ Assert.Equal(3, basic.Count);
+ Assert.Equal(1, (long)basic["a"]);
+ Assert.Equal(2, (long)basic["b"]);
+ Assert.Equal(3, (long)basic["c"]);
+ }
+
+ [Theory]
+ [InlineData(10)]
+ [InlineData(100)]
+ [InlineData(1000)]
+ [InlineData(10000)]
+ public void HashScanNoValuesLarge(int pageSize)
+ {
+ using var conn = Create(require: RedisFeatures.v7_4_0_rc1);
+
+ RedisKey key = Me() + pageSize;
+ var db = conn.GetDatabase();
+ db.KeyDelete(key, CommandFlags.FireAndForget);
+
+ for (int i = 0; i < 2000; i++)
+ {
+ db.HashSet(key, "k" + i, "v" + i, flags: CommandFlags.FireAndForget);
+ }
+
+ int count = db.HashScanNoValues(key, pageSize: pageSize).Count();
+ Assert.Equal(2000, count);
+ }
+
///
/// See .
///