Skip to content

Commit 6b37116

Browse files
HRANDFIELD feature (#2090)
Implements [`HRANDFIELD`](https://redis.io/commands/hrandfield/) as a part of #2055. Co-authored-by: Nick Craver <[email protected]>
1 parent 567527d commit 6b37116

File tree

10 files changed

+199
-1
lines changed

10 files changed

+199
-1
lines changed

docs/ReleaseNotes.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
- Adds: Support for `LPOS` with `.ListPosition()`/`.ListPositionAsync()` and `.ListPositions()`/`.ListPositionsAsync()` ([#2080 by slorello89](https://github.com/StackExchange/StackExchange.Redis/pull/2080))
1717
- Fix: For streams, properly hash `XACK`, `XCLAIM`, and `XPENDING` in cluster scenarios to eliminate `MOVED` retries ([#2085 by nielsderdaele](https://github.com/StackExchange/StackExchange.Redis/pull/2085))
1818
- Adds: Support for `OBJECT REFCOUNT` with `.KeyRefCount()`/`.KeyRefCountAsync()` ([#2087 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2087))
19-
- Adds: Support for `OBJECT ENCODIND` with `.KeyEncoding()`/`.KeyEncodingAsync()` ([#2088 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2088))
19+
- Adds: Support for `OBJECT ENCODING` with `.KeyEncoding()`/`.KeyEncodingAsync()` ([#2088 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2088))
20+
- Adds: Support for `HRANDFIELD` with `.HashRandomField()`/`.HashRandomFieldAsync()`, `.HashRandomFields()`/`.HashRandomFieldsAsync()`, and `.HashRandomFieldsWithValues()`/`.HashRandomFieldsWithValuesAsync()` ([#2090 by slorello89](https://github.com/StackExchange/StackExchange.Redis/pull/2090))
2021

2122
## 2.5.61
2223

src/StackExchange.Redis/Enums/RedisCommand.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ internal enum RedisCommand
6565
HLEN,
6666
HMGET,
6767
HMSET,
68+
HRANDFIELD,
6869
HSCAN,
6970
HSET,
7071
HSETNX,

src/StackExchange.Redis/Interfaces/IDatabase.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,35 @@ public interface IDatabase : IRedis, IDatabaseAsync
337337
/// <remarks>https://redis.io/commands/hlen</remarks>
338338
long HashLength(RedisKey key, CommandFlags flags = CommandFlags.None);
339339

340+
/// <summary>
341+
/// Gets a random field from the hash at <paramref name="key"/>.
342+
/// </summary>
343+
/// <param name="key">The key of the hash.</param>
344+
/// <param name="flags">The flags to use for this operation.</param>
345+
/// <returns>A random hash field name or <see cref="RedisValue.Null"/> if the hash does not exist.</returns>
346+
/// <remarks>https://redis.io/commands/hrandfield</remarks>
347+
RedisValue HashRandomField(RedisKey key, CommandFlags flags = CommandFlags.None);
348+
349+
/// <summary>
350+
/// Gets <paramref name="count"/> field names from the hash at <paramref name="key"/>.
351+
/// </summary>
352+
/// <param name="key">The key of the hash.</param>
353+
/// <param name="count">The number of fields to return.</param>
354+
/// <param name="flags">The flags to use for this operation.</param>
355+
/// <returns>An array of hash field names of size of at most <paramref name="count"/>, or <see cref="Array.Empty{RedisValue}"/> if the hash does not exist.</returns>
356+
/// <remarks>https://redis.io/commands/hrandfield</remarks>
357+
RedisValue[] HashRandomFields(RedisKey key, long count, CommandFlags flags = CommandFlags.None);
358+
359+
/// <summary>
360+
/// Gets <paramref name="count"/> field names and values from the hash at <paramref name="key"/>.
361+
/// </summary>
362+
/// <param name="key">The key of the hash.</param>
363+
/// <param name="count">The number of fields to return.</param>
364+
/// <param name="flags">The flags to use for this operation.</param>
365+
/// <returns>An array of hash entries of size of at most <paramref name="count"/>, or <see cref="Array.Empty{HashEntry}"/> if the hash does not exist.</returns>
366+
/// <remarks>https://redis.io/commands/hrandfield</remarks>
367+
HashEntry[] HashRandomFieldsWithValues(RedisKey key, long count, CommandFlags flags = CommandFlags.None);
368+
340369
/// <summary>
341370
/// The HSCAN command is used to incrementally iterate over a hash.
342371
/// </summary>

src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,35 @@ public interface IDatabaseAsync : IRedisAsync
324324
/// <remarks>https://redis.io/commands/hlen</remarks>
325325
Task<long> HashLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None);
326326

327+
/// <summary>
328+
/// Gets a random field from the hash at <paramref name="key"/>.
329+
/// </summary>
330+
/// <param name="key">The key of the hash.</param>
331+
/// <param name="flags">The flags to use for this operation.</param>
332+
/// <returns>A random hash field name or <see cref="RedisValue.Null"/> if the hash does not exist.</returns>
333+
/// <remarks>https://redis.io/commands/hrandfield</remarks>
334+
Task<RedisValue> HashRandomFieldAsync(RedisKey key, CommandFlags flags = CommandFlags.None);
335+
336+
/// <summary>
337+
/// Gets <paramref name="count"/> field names from the hash at <paramref name="key"/>.
338+
/// </summary>
339+
/// <param name="key">The key of the hash.</param>
340+
/// <param name="count">The number of fields to return.</param>
341+
/// <param name="flags">The flags to use for this operation.</param>
342+
/// <returns>An array of hash field names of size of at most <paramref name="count"/>, or <see cref="Array.Empty{RedisValue}"/> if the hash does not exist.</returns>
343+
/// <remarks>https://redis.io/commands/hrandfield</remarks>
344+
Task<RedisValue[]> HashRandomFieldsAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None);
345+
346+
/// <summary>
347+
/// Gets <paramref name="count"/> field names and values from the hash at <paramref name="key"/>.
348+
/// </summary>
349+
/// <param name="key">The key of the hash.</param>
350+
/// <param name="count">The number of fields to return.</param>
351+
/// <param name="flags">The flags to use for this operation.</param>
352+
/// <returns>An array of hash entries of size of at most <paramref name="count"/>, or <see cref="Array.Empty{HashEntry}"/> if the hash does not exist.</returns>
353+
/// <remarks>https://redis.io/commands/hrandfield</remarks>
354+
Task<HashEntry[]> HashRandomFieldsWithValuesAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None);
355+
327356
/// <summary>
328357
/// The HSCAN command is used to incrementally iterate over a hash.
329358
/// Note: to resume an iteration via <i>cursor</i>, cast the original enumerable or enumerator to <see cref="IScanningCursor"/>.

src/StackExchange.Redis/KeyspaceIsolation/DatabaseWrapper.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,15 @@ public RedisValue[] HashKeys(RedisKey key, CommandFlags flags = CommandFlags.Non
9393
public long HashLength(RedisKey key, CommandFlags flags = CommandFlags.None) =>
9494
Inner.HashLength(ToInner(key), flags);
9595

96+
public RedisValue HashRandomField(RedisKey key, CommandFlags flags = CommandFlags.None) =>
97+
Inner.HashRandomField(ToInner(key), flags);
98+
99+
public RedisValue[] HashRandomFields(RedisKey key, long count, CommandFlags flags = CommandFlags.None) =>
100+
Inner.HashRandomFields(ToInner(key), count, flags);
101+
102+
public HashEntry[] HashRandomFieldsWithValues(RedisKey key, long count, CommandFlags flags = CommandFlags.None) =>
103+
Inner.HashRandomFieldsWithValues(ToInner(key), count, flags);
104+
96105
public bool HashSet(RedisKey key, RedisValue hashField, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) =>
97106
Inner.HashSet(ToInner(key), hashField, value, when, flags);
98107

src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,16 @@ public Task<RedisValue[]> HashKeysAsync(RedisKey key, CommandFlags flags = Comma
9696
public Task<long> HashLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) =>
9797
Inner.HashLengthAsync(ToInner(key), flags);
9898

99+
public Task<RedisValue> HashRandomFieldAsync(RedisKey key, CommandFlags flags = CommandFlags.None) =>
100+
Inner.HashRandomFieldAsync(ToInner(key), flags);
101+
102+
public Task<RedisValue[]> HashRandomFieldsAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) =>
103+
Inner.HashRandomFieldsAsync(ToInner(key), count, flags);
104+
105+
public Task<HashEntry[]> HashRandomFieldsWithValuesAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) =>
106+
Inner.HashRandomFieldsWithValuesAsync(ToInner(key), count, flags);
107+
108+
99109
public IAsyncEnumerable<HashEntry> HashScanAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) =>
100110
Inner.HashScanAsync(ToInner(key), pattern, pageSize, cursor, pageOffset, flags);
101111

src/StackExchange.Redis/PublicAPI.Shipped.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,9 @@ StackExchange.Redis.IDatabase.HashIncrement(StackExchange.Redis.RedisKey key, St
505505
StackExchange.Redis.IDatabase.HashIncrement(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, long value = 1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
506506
StackExchange.Redis.IDatabase.HashKeys(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]!
507507
StackExchange.Redis.IDatabase.HashLength(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
508+
StackExchange.Redis.IDatabase.HashRandomField(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue
509+
StackExchange.Redis.IDatabase.HashRandomFields(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]!
510+
StackExchange.Redis.IDatabase.HashRandomFieldsWithValues(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.HashEntry[]!
508511
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>!
509512
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>!
510513
StackExchange.Redis.IDatabase.HashSet(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void
@@ -707,6 +710,9 @@ StackExchange.Redis.IDatabaseAsync.HashIncrementAsync(StackExchange.Redis.RedisK
707710
StackExchange.Redis.IDatabaseAsync.HashIncrementAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue hashField, long value = 1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<long>!
708711
StackExchange.Redis.IDatabaseAsync.HashKeysAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<StackExchange.Redis.RedisValue[]!>!
709712
StackExchange.Redis.IDatabaseAsync.HashLengthAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<long>!
713+
StackExchange.Redis.IDatabaseAsync.HashRandomFieldAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<StackExchange.Redis.RedisValue>!
714+
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[]!>!
715+
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[]!>!
710716
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>!
711717
StackExchange.Redis.IDatabaseAsync.HashSetAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.HashEntry[]! hashFields, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
712718
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>!

src/StackExchange.Redis/RedisDatabase.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,12 +402,48 @@ public long HashLength(RedisKey key, CommandFlags flags = CommandFlags.None)
402402
return ExecuteSync(msg, ResultProcessor.Int64);
403403
}
404404

405+
public RedisValue HashRandomField(RedisKey key, CommandFlags flags = CommandFlags.None)
406+
{
407+
var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key);
408+
return ExecuteSync(msg, ResultProcessor.RedisValue);
409+
}
410+
411+
public RedisValue[] HashRandomFields(RedisKey key, long count, CommandFlags flags = CommandFlags.None)
412+
{
413+
var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key, count);
414+
return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty<RedisValue>());
415+
}
416+
417+
public HashEntry[] HashRandomFieldsWithValues(RedisKey key, long count, CommandFlags flags = CommandFlags.None)
418+
{
419+
var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key, count, RedisLiterals.WITHVALUES);
420+
return ExecuteSync(msg, ResultProcessor.HashEntryArray, defaultValue: Array.Empty<HashEntry>());
421+
}
422+
405423
public Task<long> HashLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
406424
{
407425
var msg = Message.Create(Database, flags, RedisCommand.HLEN, key);
408426
return ExecuteAsync(msg, ResultProcessor.Int64);
409427
}
410428

429+
public Task<RedisValue> HashRandomFieldAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
430+
{
431+
var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key);
432+
return ExecuteAsync(msg, ResultProcessor.RedisValue);
433+
}
434+
435+
public Task<RedisValue[]> HashRandomFieldsAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None)
436+
{
437+
var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key, count);
438+
return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty<RedisValue>());
439+
}
440+
441+
public Task<HashEntry[]> HashRandomFieldsWithValuesAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None)
442+
{
443+
var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key, count, RedisLiterals.WITHVALUES);
444+
return ExecuteAsync(msg, ResultProcessor.HashEntryArray, defaultValue: Array.Empty<HashEntry>());
445+
}
446+
411447
IEnumerable<HashEntry> IDatabase.HashScan(RedisKey key, RedisValue pattern, int pageSize, CommandFlags flags)
412448
=> HashScanAsync(key, pattern, pageSize, CursorUtils.Origin, 0, flags);
413449

src/StackExchange.Redis/RedisLiterals.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ public static readonly RedisValue
110110
TYPE = "TYPE",
111111
WEIGHTS = "WEIGHTS",
112112
WITHSCORES = "WITHSCORES",
113+
WITHVALUES = "WITHVALUES",
113114
XOR = "XOR",
114115
XX = "XX",
115116

tests/StackExchange.Redis.Tests/Hashes.cs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,5 +596,81 @@ public async Task TestWhenAlwaysAsync()
596596
Assert.False(result4, "Duplicate se key 1 variant");
597597
}
598598
}
599+
600+
[Fact]
601+
public async Task HashRandomFieldAsync()
602+
{
603+
using var muxer = Create();
604+
Skip.IfBelow(muxer, RedisFeatures.v6_2_0);
605+
606+
var db = muxer.GetDatabase();
607+
var hashKey = Me();
608+
var items = new HashEntry[] { new("new york", "yankees"), new("baltimore", "orioles"), new("boston", "red sox"), new("Tampa Bay", "rays"), new("Toronto", "blue jays") };
609+
await db.HashSetAsync(hashKey, items);
610+
611+
var singleField = await db.HashRandomFieldAsync(hashKey);
612+
var multiFields = await db.HashRandomFieldsAsync(hashKey, 3);
613+
var withValues = await db.HashRandomFieldsWithValuesAsync(hashKey, 3);
614+
Assert.Equal(3, multiFields.Length);
615+
Assert.Equal(3, withValues.Length);
616+
Assert.Contains(items, x => x.Name == singleField);
617+
618+
foreach (var field in multiFields)
619+
{
620+
Assert.Contains(items, x => x.Name == field);
621+
}
622+
623+
foreach (var field in withValues)
624+
{
625+
Assert.Contains(items, x => x.Name == field.Name);
626+
}
627+
}
628+
629+
[Fact]
630+
public void HashRandomField()
631+
{
632+
using var muxer = Create();
633+
Skip.IfBelow(muxer, RedisFeatures.v6_2_0);
634+
635+
var db = muxer.GetDatabase();
636+
var hashKey = Me();
637+
var items = new HashEntry[] { new("new york", "yankees"), new("baltimore", "orioles"), new("boston", "red sox"), new("Tampa Bay", "rays"), new("Toronto", "blue jays") };
638+
db.HashSet(hashKey, items);
639+
640+
var singleField = db.HashRandomField(hashKey);
641+
var multiFields = db.HashRandomFields(hashKey, 3);
642+
var withValues = db.HashRandomFieldsWithValues(hashKey, 3);
643+
Assert.Equal(3, multiFields.Length);
644+
Assert.Equal(3, withValues.Length);
645+
Assert.Contains(items, x => x.Name == singleField);
646+
647+
foreach (var field in multiFields)
648+
{
649+
Assert.Contains(items, x => x.Name == field);
650+
}
651+
652+
foreach (var field in withValues)
653+
{
654+
Assert.Contains(items, x => x.Name == field.Name);
655+
}
656+
}
657+
658+
[Fact]
659+
public void HashRandomFieldEmptyHash()
660+
{
661+
using var muxer = Create();
662+
Skip.IfBelow(muxer, RedisFeatures.v6_2_0);
663+
664+
var db = muxer.GetDatabase();
665+
var hashKey = Me();
666+
667+
var singleField = db.HashRandomField(hashKey);
668+
var multiFields = db.HashRandomFields(hashKey, 3);
669+
var withValues = db.HashRandomFieldsWithValues(hashKey, 3);
670+
671+
Assert.Equal(RedisValue.Null, singleField);
672+
Assert.Empty(multiFields);
673+
Assert.Empty(withValues);
674+
}
599675
}
600676
}

0 commit comments

Comments
 (0)