Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for HSCAN NOVALUES #2722

Merged
merged 10 commits into from
Aug 18, 2024
27 changes: 26 additions & 1 deletion src/StackExchange.Redis/Interfaces/IDatabase.cs
Original file line number Diff line number Diff line change
@@ -469,6 +469,31 @@ public interface IDatabase : IRedis, IDatabaseAsync
/// <remarks><seealso href="https://redis.io/commands/hscan"/></remarks>
IEnumerable<HashEntry> HashScan(RedisKey key, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None);

/// <summary>
/// The HSCAN command is used to incrementally iterate over a hash and return only field names.
/// </summary>
/// <param name="key">The key of the hash.</param>
/// <param name="pattern">The pattern of keys to get entries for.</param>
/// <param name="pageSize">The page size to iterate by.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>Yields all elements of the hash matching the pattern.</returns>
/// <remarks><seealso href="https://redis.io/commands/hscan"/></remarks>
IEnumerable<RedisValue> HashScanNoValues(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags);

/// <summary>
/// The HSCAN command is used to incrementally iterate over a hash and return only field names.
/// Note: to resume an iteration via <i>cursor</i>, cast the original enumerable or enumerator to <see cref="IScanningCursor"/>.
/// </summary>
/// <param name="key">The key of the hash.</param>
/// <param name="pattern">The pattern of keys to get entries for.</param>
/// <param name="pageSize">The page size to iterate by.</param>
/// <param name="cursor">The cursor position to start at.</param>
/// <param name="pageOffset">The page offset to start at.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>Yields all elements of the hash matching the pattern.</returns>
/// <remarks><seealso href="https://redis.io/commands/hscan"/></remarks>
IEnumerable<RedisValue> HashScanNoValues(RedisKey key, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None);

/// <summary>
/// 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.
@@ -1628,7 +1653,7 @@ public interface IDatabase : IRedis, IDatabaseAsync

/// <inheritdoc cref="SortedSetAdd(RedisKey, RedisValue, double, SortedSetWhen, CommandFlags)" />
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
bool SortedSetAdd(RedisKey key, RedisValue member, double score, When when, CommandFlags flags= CommandFlags.None);
bool SortedSetAdd(RedisKey key, RedisValue member, double score, When when, CommandFlags flags = CommandFlags.None);

/// <summary>
/// Adds the specified member with the specified score to the sorted set stored at key.
14 changes: 14 additions & 0 deletions src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
Original file line number Diff line number Diff line change
@@ -445,6 +445,20 @@ public interface IDatabaseAsync : IRedisAsync
/// <remarks><seealso href="https://redis.io/commands/hscan"/></remarks>
IAsyncEnumerable<HashEntry> HashScanAsync(RedisKey key, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None);

/// <summary>
/// The HSCAN command is used to incrementally iterate over a hash and get the fields without values
/// Note: to resume an iteration via <i>cursor</i>, cast the original enumerable or enumerator to <see cref="IScanningCursor"/>.
/// </summary>
/// <param name="key">The key of the hash.</param>
/// <param name="pattern">The pattern of keys to get entries for.</param>
/// <param name="pageSize">The page size to iterate by.</param>
/// <param name="cursor">The cursor position to start at.</param>
/// <param name="pageOffset">The page offset to start at.</param>
/// <param name="flags">The flags to use for this operation.</param>
/// <returns>Yields all field names of hash matching the pattern .</returns>
/// <remarks><seealso href="https://redis.io/commands/hscan"/></remarks>
IAsyncEnumerable<RedisValue> HashScanNoValuesAsync(RedisKey key, RedisValue pattern = default, int pageSize = RedisBase.CursorUtils.DefaultLibraryPageSize, long cursor = RedisBase.CursorUtils.Origin, int pageOffset = 0, CommandFlags flags = CommandFlags.None);

/// <summary>
/// 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.
4 changes: 3 additions & 1 deletion src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs
Original file line number Diff line number Diff line change
@@ -117,10 +117,12 @@ public Task<RedisValue[]> HashRandomFieldsAsync(RedisKey key, long count, Comman
public Task<HashEntry[]> HashRandomFieldsWithValuesAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) =>
Inner.HashRandomFieldsWithValuesAsync(ToInner(key), count, flags);


public IAsyncEnumerable<HashEntry> HashScanAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) =>
Inner.HashScanAsync(ToInner(key), pattern, pageSize, cursor, pageOffset, flags);

public IAsyncEnumerable<RedisValue> HashScanNoValuesAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) =>
Inner.HashScanNoValuesAsync(ToInner(key), pattern, pageSize, cursor, pageOffset, flags);

public Task<bool> HashSetAsync(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) =>
Inner.HashSetAsync(ToInner(key), hashField, value, when, flags);

16 changes: 11 additions & 5 deletions src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ public bool GeoAdd(RedisKey key, GeoEntry value, CommandFlags flags = CommandFla
public bool GeoRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) =>
Inner.GeoRemove(ToInner(key), member, flags);

public double? GeoDistance(RedisKey key, RedisValue member1, RedisValue member2, GeoUnit unit = GeoUnit.Meters,CommandFlags flags = CommandFlags.None) =>
public double? GeoDistance(RedisKey key, RedisValue member1, RedisValue member2, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None) =>
Inner.GeoDistance(ToInner(key), member1, member2, unit, flags);

public string?[] GeoHash(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) =>
@@ -48,7 +48,7 @@ public bool GeoRemove(RedisKey key, RedisValue member, CommandFlags flags = Comm
public GeoPosition? GeoPosition(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) =>
Inner.GeoPosition(ToInner(key), member, flags);

public GeoRadiusResult[] GeoRadius(RedisKey key, RedisValue member, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null,GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) =>
public GeoRadiusResult[] GeoRadius(RedisKey key, RedisValue member, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) =>
Inner.GeoRadius(ToInner(key), member, radius, unit, count, order, options, flags);

public GeoRadiusResult[] GeoRadius(RedisKey key, double longitude, double latitude, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) =>
@@ -407,7 +407,7 @@ public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, CommandFlags fla
public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) =>
Inner.SortedSetAdd(ToInner(key), values, when, flags);

public long SortedSetAdd(RedisKey key, SortedSetEntry[] values,SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) =>
public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) =>
Inner.SortedSetAdd(ToInner(key), values, when, flags);

public bool SortedSetAdd(RedisKey key, RedisValue member, double score, CommandFlags flags) =>
@@ -510,7 +510,7 @@ public long SortedSetRemoveRangeByValue(RedisKey key, RedisValue min, RedisValue
public double?[] SortedSetScores(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) =>
Inner.SortedSetScores(ToInner(key), members, flags);

public long SortedSetUpdate(RedisKey key, SortedSetEntry[] values,SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) =>
public long SortedSetUpdate(RedisKey key, SortedSetEntry[] values, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) =>
Inner.SortedSetUpdate(ToInner(key), values, when, flags);

public bool SortedSetUpdate(RedisKey key, RedisValue member, double score, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) =>
@@ -706,8 +706,14 @@ IEnumerable<HashEntry> IDatabase.HashScan(RedisKey key, RedisValue pattern, int
IEnumerable<HashEntry> IDatabase.HashScan(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags)
=> Inner.HashScan(ToInner(key), pattern, pageSize, cursor, pageOffset, flags);

IEnumerable<RedisValue> IDatabase.HashScanNoValues(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags)
=> Inner.HashScanNoValues(ToInner(key), pattern, pageSize, flags);

IEnumerable<RedisValue> IDatabase.HashScanNoValues(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags)
=> Inner.HashScanNoValues(ToInner(key), pattern, pageSize, cursor, pageOffset, flags);

IEnumerable<RedisValue> IDatabase.SetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags)
=> Inner.SetScan(ToInner(key), pattern, pageSize, flags);
=> Inner.SetScan(ToInner(key), pattern, pageSize, flags);

IEnumerable<RedisValue> IDatabase.SetScan(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags)
=> Inner.SetScan(ToInner(key), pattern, pageSize, cursor, pageOffset, flags);
39 changes: 38 additions & 1 deletion src/StackExchange.Redis/Message.cs
Original file line number Diff line number Diff line change
@@ -282,6 +282,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);

@@ -442,7 +445,7 @@ internal static Message Create(int db, CommandFlags flags, RedisCommand command,
3 => new CommandKeyKeyValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2]),
4 => new CommandKeyKeyValueValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], values[3]),
5 => new CommandKeyKeyValueValueValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], values[3], values[4]),
6 => new CommandKeyKeyValueValueValueValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], values[3],values[4],values[5]),
6 => new CommandKeyKeyValueValueValueValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], values[3], values[4], values[5]),
7 => new CommandKeyKeyValueValueValueValueValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], values[3], values[4], values[5], values[6]),
_ => new CommandKeyKeyValuesMessage(db, flags, command, key0, key1, values),
};
@@ -1189,6 +1192,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;
3 changes: 3 additions & 0 deletions src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
@@ -552,6 +552,8 @@ 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.HashEntry>!
StackExchange.Redis.IDatabase.HashScan(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pattern, int pageSize, StackExchange.Redis.CommandFlags flags) -> System.Collections.Generic.IEnumerable<StackExchange.Redis.HashEntry>!
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.RedisValue>!
StackExchange.Redis.IDatabase.HashScanNoValues(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue pattern, int pageSize, StackExchange.Redis.CommandFlags flags) -> System.Collections.Generic.IEnumerable<StackExchange.Redis.RedisValue>!
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
@@ -782,6 +784,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.RedisValue[]!>!
StackExchange.Redis.IDatabaseAsync.HashRandomFieldsWithValuesAsync(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<StackExchange.Redis.HashEntry[]!>!
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.HashEntry>!
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.RedisValue>!
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<bool>!
StackExchange.Redis.IDatabaseAsync.HashStringLengthAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<long>!
134 changes: 87 additions & 47 deletions src/StackExchange.Redis/RedisDatabase.cs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/StackExchange.Redis/RedisLiterals.cs
Original file line number Diff line number Diff line change
@@ -108,6 +108,7 @@ public static readonly RedisValue
NODES = "NODES",
NOSAVE = "NOSAVE",
NOT = "NOT",
NOVALUES = "NOVALUES",
NUMPAT = "NUMPAT",
NUMSUB = "NUMSUB",
NX = "NX",
85 changes: 85 additions & 0 deletions tests/StackExchange.Redis.Tests/HashTests.cs
Original file line number Diff line number Diff line change
@@ -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<RedisValue>)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<RedisValue>)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()
{
15 changes: 15 additions & 0 deletions tests/StackExchange.Redis.Tests/KeyPrefixedDatabaseTests.cs
Original file line number Diff line number Diff line change
@@ -162,6 +162,21 @@ 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, 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()
{
1 change: 1 addition & 0 deletions tests/StackExchange.Redis.Tests/NamingTests.cs
Original file line number Diff line number Diff line change
@@ -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;
}
53 changes: 53 additions & 0 deletions tests/StackExchange.Redis.Tests/ScanTests.cs
Original file line number Diff line number Diff line change
@@ -334,6 +334,59 @@ 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);
}

/// <summary>
/// See <see href="https://github.com/StackExchange/StackExchange.Redis/issues/729"/>.
/// </summary>