Skip to content

Commit c0bb4eb

Browse files
atakavciNickCraver
andauthored
Add support for HSCAN NOVALUES (#2722)
Closes/Fixes #2721 Brings new functions to the API ; - IDatabase.HashScanNoValues - IDatabase.HashScanNoValues - IDatabaseAsync.HashScanNoValuesAsync ...to enable the return type consisting of keys in the hash. Added some unit and integration tests in paralleled to what is there for `HashScan` and `HashScanAsnyc`. Co-authored-by: Nick Craver <nrcraver@gmail.com>
1 parent 3701b50 commit c0bb4eb

13 files changed

+247
-4
lines changed

docs/ReleaseNotes.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Current package versions:
99
## Unreleased
1010

1111
- 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]))
12+
- 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))
1213

1314
## 2.8.0
1415

src/StackExchange.Redis/Interfaces/IDatabase.cs

+14
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,20 @@ public interface IDatabase : IRedis, IDatabaseAsync
628628
/// <remarks><seealso href="https://redis.io/commands/hscan"/></remarks>
629629
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);
630630

631+
/// <summary>
632+
/// The HSCAN command is used to incrementally iterate over a hash and return only field names.
633+
/// Note: to resume an iteration via <i>cursor</i>, cast the original enumerable or enumerator to <see cref="IScanningCursor"/>.
634+
/// </summary>
635+
/// <param name="key">The key of the hash.</param>
636+
/// <param name="pattern">The pattern of keys to get entries for.</param>
637+
/// <param name="pageSize">The page size to iterate by.</param>
638+
/// <param name="cursor">The cursor position to start at.</param>
639+
/// <param name="pageOffset">The page offset to start at.</param>
640+
/// <param name="flags">The flags to use for this operation.</param>
641+
/// <returns>Yields all elements of the hash matching the pattern.</returns>
642+
/// <remarks><seealso href="https://redis.io/commands/hscan"/></remarks>
643+
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);
644+
631645
/// <summary>
632646
/// Sets the specified fields to their respective values in the hash stored at key.
633647
/// This command overwrites any specified fields that already exist in the hash, leaving other unspecified fields untouched.

src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs

+3
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ public interface IDatabaseAsync : IRedisAsync
135135
/// <inheritdoc cref="IDatabase.HashScan(RedisKey, RedisValue, int, long, int, CommandFlags)"/>
136136
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);
137137

138+
/// <inheritdoc cref="IDatabase.HashScanNoValues(RedisKey, RedisValue, int, long, int, CommandFlags)"/>
139+
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);
140+
138141
/// <inheritdoc cref="IDatabase.HashSet(RedisKey, HashEntry[], CommandFlags)"/>
139142
Task HashSetAsync(RedisKey key, HashEntry[] hashFields, CommandFlags flags = CommandFlags.None);
140143

src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs

+3
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ public Task<HashEntry[]> HashRandomFieldsWithValuesAsync(RedisKey key, long coun
135135
public IAsyncEnumerable<HashEntry> HashScanAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) =>
136136
Inner.HashScanAsync(ToInner(key), pattern, pageSize, cursor, pageOffset, flags);
137137

138+
public IAsyncEnumerable<RedisValue> HashScanNoValuesAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) =>
139+
Inner.HashScanNoValuesAsync(ToInner(key), pattern, pageSize, cursor, pageOffset, flags);
140+
138141
public Task<bool> HashSetAsync(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) =>
139142
Inner.HashSetAsync(ToInner(key), hashField, value, when, flags);
140143

src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixedDatabase.cs

+3
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,9 @@ IEnumerable<HashEntry> IDatabase.HashScan(RedisKey key, RedisValue pattern, int
721721
IEnumerable<HashEntry> IDatabase.HashScan(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags)
722722
=> Inner.HashScan(ToInner(key), pattern, pageSize, cursor, pageOffset, flags);
723723

724+
IEnumerable<RedisValue> IDatabase.HashScanNoValues(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags)
725+
=> Inner.HashScanNoValues(ToInner(key), pattern, pageSize, cursor, pageOffset, flags);
726+
724727
IEnumerable<RedisValue> IDatabase.SetScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags)
725728
=> Inner.SetScan(ToInner(key), pattern, pageSize, flags);
726729

src/StackExchange.Redis/Message.cs

+37
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,9 @@ public static Message Create(int db, CommandFlags flags, RedisCommand command, i
292292
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) =>
293293
new CommandKeyValueValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3, value4);
294294

295+
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) =>
296+
new CommandKeyValueValueValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3, value4, value5);
297+
295298
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) =>
296299
new CommandKeyValueValueValueValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3, value4, value5, value6);
297300

@@ -1276,6 +1279,40 @@ protected override void WriteImpl(PhysicalConnection physical)
12761279
public override int ArgCount => 6;
12771280
}
12781281

1282+
private sealed class CommandKeyValueValueValueValueValueValueMessage : CommandKeyBase
1283+
{
1284+
private readonly RedisValue value0, value1, value2, value3, value4, value5;
1285+
1286+
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)
1287+
{
1288+
value0.AssertNotNull();
1289+
value1.AssertNotNull();
1290+
value2.AssertNotNull();
1291+
value3.AssertNotNull();
1292+
value4.AssertNotNull();
1293+
value5.AssertNotNull();
1294+
this.value0 = value0;
1295+
this.value1 = value1;
1296+
this.value2 = value2;
1297+
this.value3 = value3;
1298+
this.value4 = value4;
1299+
this.value5 = value5;
1300+
}
1301+
1302+
protected override void WriteImpl(PhysicalConnection physical)
1303+
{
1304+
physical.WriteHeader(Command, ArgCount);
1305+
physical.Write(Key);
1306+
physical.WriteBulkString(value0);
1307+
physical.WriteBulkString(value1);
1308+
physical.WriteBulkString(value2);
1309+
physical.WriteBulkString(value3);
1310+
physical.WriteBulkString(value4);
1311+
physical.WriteBulkString(value5);
1312+
}
1313+
public override int ArgCount => 7;
1314+
}
1315+
12791316
private sealed class CommandKeyValueValueValueValueValueValueValueMessage : CommandKeyBase
12801317
{
12811318
private readonly RedisValue value0, value1, value2, value3, value4, value5, value6;

src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt

+2
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,7 @@ StackExchange.Redis.IDatabase.HashRandomFields(StackExchange.Redis.RedisKey key,
581581
StackExchange.Redis.IDatabase.HashRandomFieldsWithValues(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.HashEntry[]!
582582
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>!
583583
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>!
584+
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>!
584585
StackExchange.Redis.IDatabase.HashSet(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void
585586
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
586587
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
818819
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[]!>!
819820
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[]!>!
820821
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>!
822+
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>!
821823
StackExchange.Redis.IDatabaseAsync.HashSetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
822824
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>!
823825
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>!

src/StackExchange.Redis/RedisDatabase.cs

+31-4
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,23 @@ private CursorEnumerable<HashEntry> HashScanAsync(RedisKey key, RedisValue patte
624624
throw ExceptionFactory.NotSupported(true, RedisCommand.HSCAN);
625625
}
626626

627+
IEnumerable<RedisValue> IDatabase.HashScanNoValues(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags)
628+
=> HashScanNoValuesAsync(key, pattern, pageSize, cursor, pageOffset, flags);
629+
630+
IAsyncEnumerable<RedisValue> IDatabaseAsync.HashScanNoValuesAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags)
631+
=> HashScanNoValuesAsync(key, pattern, pageSize, cursor, pageOffset, flags);
632+
633+
private CursorEnumerable<RedisValue> HashScanNoValuesAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags)
634+
{
635+
var scan = TryScan<RedisValue>(key, pattern, pageSize, cursor, pageOffset, flags, RedisCommand.HSCAN, SetScanResultProcessor.Default, out var server, true);
636+
if (scan != null) return scan;
637+
638+
if (cursor != 0) throw ExceptionFactory.NoCursor(RedisCommand.HKEYS);
639+
640+
if (pattern.IsNull) return CursorEnumerable<RedisValue>.From(this, server, HashKeysAsync(key, flags), pageOffset);
641+
throw ExceptionFactory.NotSupported(true, RedisCommand.HSCAN);
642+
}
643+
627644
public bool HashSet(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None)
628645
{
629646
WhenAlwaysOrNotExists(when);
@@ -4679,7 +4696,7 @@ private Message GetStringSetAndGetMessage(
46794696
_ => throw new ArgumentOutOfRangeException(nameof(operation)),
46804697
};
46814698

4682-
private CursorEnumerable<T>? TryScan<T>(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags, RedisCommand command, ResultProcessor<ScanEnumerable<T>.ScanResult> processor, out ServerEndPoint? server)
4699+
private CursorEnumerable<T>? TryScan<T>(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags, RedisCommand command, ResultProcessor<ScanEnumerable<T>.ScanResult> processor, out ServerEndPoint? server, bool noValues = false)
46834700
{
46844701
server = null;
46854702
if (pageSize <= 0)
@@ -4690,7 +4707,7 @@ private Message GetStringSetAndGetMessage(
46904707
if (!features.Scan) return null;
46914708

46924709
if (CursorUtils.IsNil(pattern)) pattern = (byte[]?)null;
4693-
return new ScanEnumerable<T>(this, server, key, pattern, pageSize, cursor, pageOffset, flags, command, processor);
4710+
return new ScanEnumerable<T>(this, server, key, pattern, pageSize, cursor, pageOffset, flags, command, processor, noValues);
46944711
}
46954712

46964713
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<T> : CursorEnumerable<T>
47834800
private readonly RedisKey key;
47844801
private readonly RedisValue pattern;
47854802
private readonly RedisCommand command;
4803+
private readonly bool noValues;
47864804

47874805
public ScanEnumerable(
47884806
RedisDatabase database,
@@ -4794,19 +4812,28 @@ public ScanEnumerable(
47944812
int pageOffset,
47954813
CommandFlags flags,
47964814
RedisCommand command,
4797-
ResultProcessor<ScanResult> processor)
4815+
ResultProcessor<ScanResult> processor,
4816+
bool noValues)
47984817
: base(database, server, database.Database, pageSize, cursor, pageOffset, flags)
47994818
{
48004819
this.key = key;
48014820
this.pattern = pattern;
48024821
this.command = command;
48034822
Processor = processor;
4823+
this.noValues = noValues;
48044824
}
48054825

48064826
private protected override ResultProcessor<CursorEnumerable<T>.ScanResult> Processor { get; }
48074827

48084828
private protected override Message CreateMessage(in RedisValue cursor)
48094829
{
4830+
if (noValues)
4831+
{
4832+
if (CursorUtils.IsNil(pattern) && pageSize == CursorUtils.DefaultRedisPageSize) return Message.Create(db, flags, command, key, cursor, RedisLiterals.NOVALUES);
4833+
if (CursorUtils.IsNil(pattern)) return Message.Create(db, flags, command, key, cursor, RedisLiterals.COUNT, pageSize, RedisLiterals.NOVALUES);
4834+
return Message.Create(db, flags, command, key, cursor, RedisLiterals.MATCH, pattern, RedisLiterals.COUNT, pageSize, RedisLiterals.NOVALUES);
4835+
}
4836+
48104837
if (CursorUtils.IsNil(pattern))
48114838
{
48124839
if (pageSize == CursorUtils.DefaultRedisPageSize)
@@ -4826,7 +4853,7 @@ private protected override Message CreateMessage(in RedisValue cursor)
48264853
}
48274854
else
48284855
{
4829-
return Message.Create(db, flags, command, key, new RedisValue[] { cursor, RedisLiterals.MATCH, pattern, RedisLiterals.COUNT, pageSize });
4856+
return Message.Create(db, flags, command, key, cursor, RedisLiterals.MATCH, pattern, RedisLiterals.COUNT, pageSize);
48304857
}
48314858
}
48324859
}

src/StackExchange.Redis/RedisLiterals.cs

+1
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ public static readonly RedisValue
113113
NODES = "NODES",
114114
NOSAVE = "NOSAVE",
115115
NOT = "NOT",
116+
NOVALUES = "NOVALUES",
116117
NUMPAT = "NUMPAT",
117118
NUMSUB = "NUMSUB",
118119
NX = "NX",

0 commit comments

Comments
 (0)