Skip to content

Commit

Permalink
Add support for HSCAN NOVALUES (#2722)
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
atakavci and NickCraver authored Aug 18, 2024
1 parent 3701b50 commit c0bb4eb
Show file tree
Hide file tree
Showing 13 changed files with 247 additions and 4 deletions.
1 change: 1 addition & 0 deletions docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
14 changes: 14 additions & 0 deletions src/StackExchange.Redis/Interfaces/IDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,20 @@ 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.
/// 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.
Expand Down
3 changes: 3 additions & 0 deletions src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ public interface IDatabaseAsync : IRedisAsync
/// <inheritdoc cref="IDatabase.HashScan(RedisKey, RedisValue, int, long, int, CommandFlags)"/>
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);

/// <inheritdoc cref="IDatabase.HashScanNoValues(RedisKey, RedisValue, int, long, int, CommandFlags)"/>
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);

/// <inheritdoc cref="IDatabase.HashSet(RedisKey, HashEntry[], CommandFlags)"/>
Task HashSetAsync(RedisKey key, HashEntry[] hashFields, CommandFlags flags = CommandFlags.None);

Expand Down
3 changes: 3 additions & 0 deletions src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ public Task<HashEntry[]> HashRandomFieldsWithValuesAsync(RedisKey key, long coun
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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,9 @@ 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, 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);

Expand Down
37 changes: 37 additions & 0 deletions src/StackExchange.Redis/Message.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.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.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
Expand Down Expand Up @@ -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.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>!
Expand Down
35 changes: 31 additions & 4 deletions src/StackExchange.Redis/RedisDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,23 @@ private CursorEnumerable<HashEntry> HashScanAsync(RedisKey key, RedisValue patte
throw ExceptionFactory.NotSupported(true, RedisCommand.HSCAN);
}

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

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

private CursorEnumerable<RedisValue> HashScanNoValuesAsync(RedisKey key, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags)
{
var scan = TryScan<RedisValue>(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<RedisValue>.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);
Expand Down Expand Up @@ -4679,7 +4696,7 @@ private Message GetStringSetAndGetMessage(
_ => throw new ArgumentOutOfRangeException(nameof(operation)),
};

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)
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)
{
server = null;
if (pageSize <= 0)
Expand All @@ -4690,7 +4707,7 @@ private Message GetStringSetAndGetMessage(
if (!features.Scan) return null;

if (CursorUtils.IsNil(pattern)) pattern = (byte[]?)null;
return new ScanEnumerable<T>(this, server, key, pattern, pageSize, cursor, pageOffset, flags, command, processor);
return new ScanEnumerable<T>(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)
Expand Down Expand Up @@ -4783,6 +4800,7 @@ internal class ScanEnumerable<T> : CursorEnumerable<T>
private readonly RedisKey key;
private readonly RedisValue pattern;
private readonly RedisCommand command;
private readonly bool noValues;

public ScanEnumerable(
RedisDatabase database,
Expand All @@ -4794,19 +4812,28 @@ public ScanEnumerable(
int pageOffset,
CommandFlags flags,
RedisCommand command,
ResultProcessor<ScanResult> processor)
ResultProcessor<ScanResult> 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<CursorEnumerable<T>.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)
Expand All @@ -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);
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/StackExchange.Redis/RedisLiterals.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ public static readonly RedisValue
NODES = "NODES",
NOSAVE = "NOSAVE",
NOT = "NOT",
NOVALUES = "NOVALUES",
NUMPAT = "NUMPAT",
NUMSUB = "NUMSUB",
NX = "NX",
Expand Down
Loading

0 comments on commit c0bb4eb

Please sign in to comment.