Skip to content

Commit da3b91a

Browse files
authored
Hash Commands (GA) (#34)
1 parent 66a0cc7 commit da3b91a

10 files changed

Lines changed: 723 additions & 53 deletions

File tree

sources/Valkey.Glide/BaseClient.HashCommands.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,72 @@ public async Task<bool> HashExistsAsync(ValkeyKey key, ValkeyValue hashField, Co
5555
return await Command(Request.HashExistsAsync(key, hashField));
5656
}
5757

58+
public async Task<long> HashIncrementAsync(ValkeyKey key, ValkeyValue hashField, long value = 1, CommandFlags flags = CommandFlags.None)
59+
{
60+
Utils.Requires<NotImplementedException>(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
61+
return await Command(Request.HashIncrementAsync(key, hashField, value));
62+
}
63+
64+
public async Task<double> HashIncrementAsync(ValkeyKey key, ValkeyValue hashField, double value, CommandFlags flags = CommandFlags.None)
65+
{
66+
Utils.Requires<NotImplementedException>(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
67+
return await Command(Request.HashIncrementAsync(key, hashField, value));
68+
}
69+
70+
public async Task<ValkeyValue[]> HashKeysAsync(ValkeyKey key, CommandFlags flags = CommandFlags.None)
71+
{
72+
Utils.Requires<NotImplementedException>(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
73+
return await Command(Request.HashKeysAsync(key));
74+
}
75+
5876
public async Task<long> HashLengthAsync(ValkeyKey key, CommandFlags flags = CommandFlags.None)
5977
{
6078
Utils.Requires<NotImplementedException>(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
6179
return await Command(Request.HashLengthAsync(key));
6280
}
6381

82+
public async IAsyncEnumerable<HashEntry> HashScanAsync(ValkeyKey key, ValkeyValue pattern = default, int pageSize = 250, long cursor = 0, int pageOffset = 0, CommandFlags flags = CommandFlags.None)
83+
{
84+
Utils.Requires<NotImplementedException>(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
85+
86+
long currentCursor = cursor;
87+
88+
do
89+
{
90+
(long nextCursor, HashEntry[] entries) = await Command(Request.HashScanAsync<HashEntry[]>(key, currentCursor, pattern, pageSize, true));
91+
92+
IEnumerable<HashEntry> entriesToYield = pageOffset > 0 ? entries.Skip(pageOffset) : entries;
93+
94+
foreach (HashEntry entry in entriesToYield)
95+
{
96+
yield return entry;
97+
}
98+
99+
currentCursor = nextCursor;
100+
} while (currentCursor != 0);
101+
}
102+
103+
public async IAsyncEnumerable<ValkeyValue> HashScanNoValuesAsync(ValkeyKey key, ValkeyValue pattern = default, int pageSize = 250, long cursor = 0, int pageOffset = 0, CommandFlags flags = CommandFlags.None)
104+
{
105+
Utils.Requires<NotImplementedException>(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
106+
107+
long currentCursor = cursor;
108+
109+
do
110+
{
111+
(long nextCursor, ValkeyValue[] fields) = await Command(Request.HashScanAsync<ValkeyValue[]>(key, currentCursor, pattern, pageSize, false));
112+
113+
IEnumerable<ValkeyValue> fieldsToYield = pageOffset > 0 ? fields.Skip(pageOffset) : fields;
114+
115+
foreach (ValkeyValue field in fieldsToYield)
116+
{
117+
yield return field;
118+
}
119+
120+
currentCursor = nextCursor;
121+
} while (currentCursor != 0);
122+
}
123+
64124
public async Task<long> HashStringLengthAsync(ValkeyKey key, ValkeyValue hashField, CommandFlags flags = CommandFlags.None)
65125
{
66126
Utils.Requires<NotImplementedException>(flags == CommandFlags.None, "Command flags are not supported by GLIDE");

sources/Valkey.Glide/Commands/Constants/Constants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public static class Constants
2525
public const string AfterKeyword = "AFTER";
2626
public const string RankKeyword = "RANK";
2727
public const string MaxLenKeyword = "MAXLEN";
28+
public const string WithValuesKeyword = "WITHVALUES";
2829

2930
/// <summary>
3031
/// Expiry keywords.

sources/Valkey.Glide/Commands/IHashCommands.cs

Lines changed: 165 additions & 49 deletions
Large diffs are not rendered by default.

sources/Valkey.Glide/Internals/Request.HashCommands.cs

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
22

3+
using Valkey.Glide.Commands.Constants;
4+
35
using static Valkey.Glide.Internals.FFI;
46

57
namespace Valkey.Glide.Internals;
@@ -66,12 +68,75 @@ public static Cmd<bool, bool> HashExistsAsync(ValkeyKey key, ValkeyValue hashFie
6668
return Boolean<bool>(RequestType.HExists, args);
6769
}
6870

71+
public static Cmd<long, long> HashIncrementAsync(ValkeyKey key, ValkeyValue hashField, long value)
72+
{
73+
GlideString[] args = [key.ToGlideString(), hashField.ToGlideString(), value.ToGlideString()];
74+
return Simple<long>(RequestType.HIncrBy, args);
75+
}
76+
77+
public static Cmd<double, double> HashIncrementAsync(ValkeyKey key, ValkeyValue hashField, double value)
78+
{
79+
GlideString[] args = [key.ToGlideString(), hashField.ToGlideString(), value.ToGlideString()];
80+
return Simple<double>(RequestType.HIncrByFloat, args);
81+
}
82+
83+
public static Cmd<object[], ValkeyValue[]> HashKeysAsync(ValkeyKey key)
84+
{
85+
GlideString[] args = [key.ToGlideString()];
86+
return ObjectArrayToValkeyValueArray(RequestType.HKeys, args);
87+
}
88+
6989
public static Cmd<long, long> HashLengthAsync(ValkeyKey key)
7090
{
7191
GlideString[] args = [key.ToGlideString()];
7292
return Simple<long>(RequestType.HLen, args);
7393
}
7494

95+
public static Cmd<object[], (long, T)> HashScanAsync<T>(ValkeyKey key, long cursor, ValkeyValue pattern, long count, bool includeValues = true)
96+
{
97+
List<GlideString> args = [key.ToGlideString(), cursor.ToGlideString()];
98+
99+
if (!pattern.IsNull)
100+
{
101+
args.AddRange([Constants.MatchKeyword.ToGlideString(), pattern.ToGlideString()]);
102+
}
103+
104+
if (count > 0)
105+
{
106+
args.AddRange([Constants.CountKeyword.ToGlideString(), count.ToGlideString()]);
107+
}
108+
109+
return new(RequestType.HScan, [.. args], false, arr =>
110+
{
111+
object[] scanArray = arr;
112+
long nextCursor = long.Parse(((GlideString)scanArray[0]).ToString());
113+
object[] items = (object[])scanArray[1]; // This array will always have an even-length
114+
115+
if (includeValues)
116+
{
117+
// Return HashEntry[] with both field names and values
118+
HashEntry[] entries = new HashEntry[items.Length / 2];
119+
for (int i = 0; i < items.Length; i += 2)
120+
{
121+
ValkeyValue field = (ValkeyValue)(GlideString)items[i];
122+
ValkeyValue value = (ValkeyValue)(GlideString)items[i + 1];
123+
entries[i / 2] = new HashEntry(field, value);
124+
}
125+
return (nextCursor, (T)(object)entries);
126+
}
127+
else
128+
{
129+
// Return ValkeyValue[] with only field names
130+
ValkeyValue[] fields = new ValkeyValue[items.Length / 2];
131+
for (int i = 0; i < items.Length; i += 2)
132+
{
133+
fields[i / 2] = (ValkeyValue)(GlideString)items[i];
134+
}
135+
return (nextCursor, (T)(object)fields);
136+
}
137+
});
138+
}
139+
75140
public static Cmd<long, long> HashStringLengthAsync(ValkeyKey key, ValkeyValue hashField)
76141
{
77142
GlideString[] args = [key.ToGlideString(), hashField.ToGlideString()];
@@ -98,7 +163,7 @@ public static Cmd<object[], ValkeyValue[]> HashRandomFieldsAsync(ValkeyKey key,
98163

99164
public static Cmd<object[], HashEntry[]> HashRandomFieldsWithValuesAsync(ValkeyKey key, long count)
100165
{
101-
GlideString[] args = [key.ToGlideString(), count.ToGlideString(), "WITHVALUES"];
166+
GlideString[] args = [key.ToGlideString(), count.ToGlideString(), Constants.WithValuesKeyword];
102167
return ObjectArrayToHashEntries(RequestType.HRandField, args);
103168
}
104169

sources/Valkey.Glide/Internals/Request.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,11 @@ internal partial class Request
1212
public static Cmd<object?, T> CustomCommand<T>(GlideString[] args, Func<object?, T> converter) where T : class?
1313
=> new(RequestType.CustomCommand, args, true, converter);
1414

15-
#pragma warning disable IDE0051 // Add missing cases TODO: REMOVE ONCE 4336 IS MERGED
1615
/// <summary>
1716
/// Create a Cmd which returns OK
1817
/// </summary>
1918
private static Cmd<string, string> OK(RequestType request, GlideString[] args)
2019
=> Simple<string>(request, args);
21-
#pragma warning restore IDE0051 // Add missing cases
2220

2321
/// <summary>
2422
/// Create a Cmd which does not need type conversion
@@ -53,7 +51,7 @@ private static Cmd<string, bool> OKToBool(RequestType request, GlideString[] arg
5351
/// <param name="isNullable">Whether the response can be null</param>
5452
/// <returns>A command that converts the response to a ValkeyValue</returns>
5553
private static Cmd<GlideString, ValkeyValue> ToValkeyValue(RequestType request, GlideString[] args, bool isNullable = false)
56-
=> new(request, args, isNullable, response => (ValkeyValue)response);
54+
=> new(request, args, isNullable, response => (ValkeyValue)response, allowConverterToHandleNull: true);
5755

5856
/// <summary>
5957
/// Create a Cmd which converts an array of GlideStrings to an array of ValkeyValues.

sources/Valkey.Glide/Pipeline/BaseBatch.HashCommands.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ public abstract partial class BaseBatch<T>
3333
/// <inheritdoc cref="IBatchHashCommands.HashExists(ValkeyKey, ValkeyValue)" />
3434
public T HashExists(ValkeyKey key, ValkeyValue hashField) => AddCmd(HashExistsAsync(key, hashField));
3535

36+
/// <inheritdoc cref="IBatchHashCommands.HashIncrement(ValkeyKey, ValkeyValue, long)" />
37+
public T HashIncrement(ValkeyKey key, ValkeyValue hashField, long value = 1) => AddCmd(HashIncrementAsync(key, hashField, value));
38+
39+
/// <inheritdoc cref="IBatchHashCommands.HashIncrement(ValkeyKey, ValkeyValue, double)" />
40+
public T HashIncrement(ValkeyKey key, ValkeyValue hashField, double value) => AddCmd(HashIncrementAsync(key, hashField, value));
41+
42+
/// <inheritdoc cref="IBatchHashCommands.HashKeys(ValkeyKey)" />
43+
public T HashKeys(ValkeyKey key) => AddCmd(HashKeysAsync(key));
44+
3645
/// <inheritdoc cref="IBatchHashCommands.HashLength(ValkeyKey)" />
3746
public T HashLength(ValkeyKey key) => AddCmd(HashLengthAsync(key));
3847

@@ -51,6 +60,12 @@ public abstract partial class BaseBatch<T>
5160
/// <inheritdoc cref="IBatchHashCommands.HashRandomFieldsWithValues(ValkeyKey, long)" />
5261
public T HashRandomFieldsWithValues(ValkeyKey key, long count) => AddCmd(HashRandomFieldsWithValuesAsync(key, count));
5362

63+
/// <inheritdoc cref="IBatchHashCommands.HashScan(ValkeyKey, long, ValkeyValue, long)" />
64+
public T HashScan(ValkeyKey key, long cursor, ValkeyValue pattern = default, long count = 0) => AddCmd(HashScanAsync<HashEntry[]>(key, cursor, pattern, count, true));
65+
66+
/// <inheritdoc cref="IBatchHashCommands.HashScanNoValues(ValkeyKey, long, ValkeyValue, long)" />
67+
public T HashScanNoValues(ValkeyKey key, long cursor, ValkeyValue pattern = default, long count = 0) => AddCmd(HashScanAsync<ValkeyValue[]>(key, cursor, pattern, count, false));
68+
5469
// Explicit interface implementations for IBatchHashCommands
5570
IBatch IBatchHashCommands.HashGet(ValkeyKey key, ValkeyValue hashField) => HashGet(key, hashField);
5671
IBatch IBatchHashCommands.HashGet(ValkeyKey key, ValkeyValue[] hashFields) => HashGet(key, hashFields);
@@ -60,10 +75,15 @@ public abstract partial class BaseBatch<T>
6075
IBatch IBatchHashCommands.HashDelete(ValkeyKey key, ValkeyValue hashField) => HashDelete(key, hashField);
6176
IBatch IBatchHashCommands.HashDelete(ValkeyKey key, ValkeyValue[] hashFields) => HashDelete(key, hashFields);
6277
IBatch IBatchHashCommands.HashExists(ValkeyKey key, ValkeyValue hashField) => HashExists(key, hashField);
78+
IBatch IBatchHashCommands.HashIncrement(ValkeyKey key, ValkeyValue hashField, long value) => HashIncrement(key, hashField, value);
79+
IBatch IBatchHashCommands.HashIncrement(ValkeyKey key, ValkeyValue hashField, double value) => HashIncrement(key, hashField, value);
80+
IBatch IBatchHashCommands.HashKeys(ValkeyKey key) => HashKeys(key);
6381
IBatch IBatchHashCommands.HashLength(ValkeyKey key) => HashLength(key);
6482
IBatch IBatchHashCommands.HashStringLength(ValkeyKey key, ValkeyValue hashField) => HashStringLength(key, hashField);
6583
IBatch IBatchHashCommands.HashValues(ValkeyKey key) => HashValues(key);
6684
IBatch IBatchHashCommands.HashRandomField(ValkeyKey key) => HashRandomField(key);
6785
IBatch IBatchHashCommands.HashRandomFields(ValkeyKey key, long count) => HashRandomFields(key, count);
6886
IBatch IBatchHashCommands.HashRandomFieldsWithValues(ValkeyKey key, long count) => HashRandomFieldsWithValues(key, count);
87+
IBatch IBatchHashCommands.HashScan(ValkeyKey key, long cursor, ValkeyValue pattern, long count) => HashScan(key, cursor, pattern, count);
88+
IBatch IBatchHashCommands.HashScanNoValues(ValkeyKey key, long cursor, ValkeyValue pattern, long count) => HashScanNoValues(key, cursor, pattern, count);
6989
}

sources/Valkey.Glide/Pipeline/IBatchHashCommands.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,18 @@ internal interface IBatchHashCommands
3838
/// <returns>Command Response - <inheritdoc cref="IHashCommands.HashExistsAsync(ValkeyKey, ValkeyValue, CommandFlags)" /></returns>
3939
IBatch HashExists(ValkeyKey key, ValkeyValue hashField);
4040

41+
/// <inheritdoc cref="IHashCommands.HashIncrementAsync(ValkeyKey, ValkeyValue, long, CommandFlags)" path="/*[not(self::remarks) and not(self::returns)]" />
42+
/// <returns>Command Response - <inheritdoc cref="IHashCommands.HashIncrementAsync(ValkeyKey, ValkeyValue, long, CommandFlags)" /></returns>
43+
IBatch HashIncrement(ValkeyKey key, ValkeyValue hashField, long value = 1);
44+
45+
/// <inheritdoc cref="IHashCommands.HashIncrementAsync(ValkeyKey, ValkeyValue, double, CommandFlags)" path="/*[not(self::remarks) and not(self::returns)]" />
46+
/// <returns>Command Response - <inheritdoc cref="IHashCommands.HashIncrementAsync(ValkeyKey, ValkeyValue, double, CommandFlags)" /></returns>
47+
IBatch HashIncrement(ValkeyKey key, ValkeyValue hashField, double value);
48+
49+
/// <inheritdoc cref="IHashCommands.HashKeysAsync(ValkeyKey, CommandFlags)" path="/*[not(self::remarks) and not(self::returns)]" />
50+
/// <returns>Command Response - <inheritdoc cref="IHashCommands.HashKeysAsync(ValkeyKey, CommandFlags)" /></returns>
51+
IBatch HashKeys(ValkeyKey key);
52+
4153
/// <inheritdoc cref="IHashCommands.HashLengthAsync(ValkeyKey, CommandFlags)" path="/*[not(self::remarks) and not(self::returns)]" />
4254
/// <returns>Command Response - <inheritdoc cref="IHashCommands.HashLengthAsync(ValkeyKey, CommandFlags)" /></returns>
4355
IBatch HashLength(ValkeyKey key);
@@ -61,4 +73,12 @@ internal interface IBatchHashCommands
6173
/// <inheritdoc cref="IHashCommands.HashRandomFieldsWithValuesAsync(ValkeyKey, long, CommandFlags)" path="/*[not(self::remarks) and not(self::returns)]" />
6274
/// <returns>Command Response - <inheritdoc cref="IHashCommands.HashRandomFieldsWithValuesAsync(ValkeyKey, long, CommandFlags)" /></returns>
6375
IBatch HashRandomFieldsWithValues(ValkeyKey key, long count);
76+
77+
/// <inheritdoc cref="IHashCommands.HashScanAsync(ValkeyKey, ValkeyValue, int, long, int, CommandFlags)" path="/*[not(self::remarks) and not(self::returns)]" />
78+
/// <returns>Command Response - <inheritdoc cref="IHashCommands.HashScanAsync(ValkeyKey, ValkeyValue, int, long, int, CommandFlags)" /></returns>
79+
IBatch HashScan(ValkeyKey key, long cursor, ValkeyValue pattern = default, long count = 0);
80+
81+
/// <inheritdoc cref="IHashCommands.HashScanNoValuesAsync(ValkeyKey, ValkeyValue, int, long, int, CommandFlags)" path="/*[not(self::remarks) and not(self::returns)]" />
82+
/// <returns>Command Response - <inheritdoc cref="IHashCommands.HashScanNoValuesAsync(ValkeyKey, ValkeyValue, int, long, int, CommandFlags)" /></returns>
83+
IBatch HashScanNoValues(ValkeyKey key, long cursor, ValkeyValue pattern = default, long count = 0);
6484
}

0 commit comments

Comments
 (0)