Skip to content

Commit 21f0ece

Browse files
authored
Generic Commands (#89)
Signed-off-by: Alex Rehnby-Martin <alex.rehnby-martin@improving.com>
1 parent 2dc9a4f commit 21f0ece

File tree

14 files changed

+1120
-25
lines changed

14 files changed

+1120
-25
lines changed

sources/Valkey.Glide/BaseClient.GenericCommands.cs

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,19 @@ public async Task<long> KeyExistsAsync(ValkeyKey[] keys, CommandFlags flags = Co
4444
return await Command(Request.KeyExistsAsync(keys));
4545
}
4646

47-
public async Task<bool> KeyExpireAsync(ValkeyKey key, TimeSpan? expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None)
47+
public async Task<bool> KeyExpireAsync(ValkeyKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None)
48+
=> await KeyExpireAsync(key, expiry, ExpireWhen.Always, flags);
49+
50+
public async Task<bool> KeyExpireAsync(ValkeyKey key, TimeSpan? expiry, ExpireWhen when, CommandFlags flags = CommandFlags.None)
4851
{
4952
Utils.Requires<NotImplementedException>(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
5053
return await Command(Request.KeyExpireAsync(key, expiry, when));
5154
}
5255

53-
public async Task<bool> KeyExpireAsync(ValkeyKey key, DateTime? expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None)
56+
public async Task<bool> KeyExpireAsync(ValkeyKey key, DateTime? expiry, CommandFlags flags = CommandFlags.None)
57+
=> await KeyExpireAsync(key, expiry, ExpireWhen.Always, flags);
58+
59+
public async Task<bool> KeyExpireAsync(ValkeyKey key, DateTime? expiry, ExpireWhen when, CommandFlags flags = CommandFlags.None)
5460
{
5561
Utils.Requires<NotImplementedException>(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
5662
return await Command(Request.KeyExpireAsync(key, expiry, when));
@@ -116,10 +122,64 @@ public async Task<long> KeyTouchAsync(ValkeyKey[] keys, CommandFlags flags = Com
116122
return await Command(Request.KeyTouchAsync(keys));
117123
}
118124

125+
public async Task<DateTime?> KeyExpireTimeAsync(ValkeyKey key, CommandFlags flags = CommandFlags.None)
126+
{
127+
Utils.Requires<NotImplementedException>(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
128+
return await Command(Request.KeyExpireTimeAsync(key));
129+
}
130+
131+
public async Task<string?> KeyEncodingAsync(ValkeyKey key, CommandFlags flags = CommandFlags.None)
132+
{
133+
Utils.Requires<NotImplementedException>(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
134+
return await Command(Request.KeyEncodingAsync(key));
135+
}
136+
137+
public async Task<long?> KeyFrequencyAsync(ValkeyKey key, CommandFlags flags = CommandFlags.None)
138+
{
139+
Utils.Requires<NotImplementedException>(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
140+
return await Command(Request.KeyFrequencyAsync(key));
141+
}
142+
143+
public async Task<long?> KeyIdleTimeAsync(ValkeyKey key, CommandFlags flags = CommandFlags.None)
144+
{
145+
Utils.Requires<NotImplementedException>(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
146+
return await Command(Request.KeyIdleTimeAsync(key));
147+
}
148+
149+
public async Task<long?> KeyRefCountAsync(ValkeyKey key, CommandFlags flags = CommandFlags.None)
150+
{
151+
Utils.Requires<NotImplementedException>(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
152+
return await Command(Request.KeyRefCountAsync(key));
153+
}
154+
119155
public async Task<bool> KeyCopyAsync(ValkeyKey sourceKey, ValkeyKey destinationKey, bool replace = false, CommandFlags flags = CommandFlags.None)
120156
{
121157
Utils.Requires<NotImplementedException>(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
122158
return await Command(Request.KeyCopyAsync(sourceKey, destinationKey, replace));
123159
}
124160

161+
public async Task<string?> KeyRandomAsync(CommandFlags flags = CommandFlags.None)
162+
{
163+
Utils.Requires<NotImplementedException>(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
164+
return await Command(Request.KeyRandomAsync());
165+
}
166+
167+
public async Task<ValkeyValue[]> SortAsync(ValkeyKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, ValkeyValue by = default, ValkeyValue[]? get = null, CommandFlags flags = CommandFlags.None)
168+
{
169+
Utils.Requires<NotImplementedException>(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
170+
return await Command(Request.SortAsync(key, skip, take, order, sortType, by, get, _serverVersion));
171+
}
172+
173+
public async Task<long> SortAndStoreAsync(ValkeyKey destination, ValkeyKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, ValkeyValue by = default, ValkeyValue[]? get = null, CommandFlags flags = CommandFlags.None)
174+
{
175+
Utils.Requires<NotImplementedException>(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
176+
return await Command(Request.SortAndStoreAsync(destination, key, skip, take, order, sortType, by, get));
177+
}
178+
179+
public async Task<long> WaitAsync(long numreplicas, long timeout, CommandFlags flags = CommandFlags.None)
180+
{
181+
Utils.Requires<NotImplementedException>(flags == CommandFlags.None, "Command flags are not supported by GLIDE");
182+
return await Command(Request.WaitAsync(numreplicas, timeout));
183+
}
184+
125185
}

sources/Valkey.Glide/BaseClient.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,15 @@ protected static async Task<T> CreateClient<T>(BaseClientConfiguration config, F
5252
Message message = client._messageContainer.GetMessageForCall();
5353
CreateClientFfi(request.ToPtr(), successCallbackPointer, failureCallbackPointer);
5454
client._clientPointer = await message; // This will throw an error thru failure callback if any
55-
return client._clientPointer != IntPtr.Zero
56-
? client
57-
: throw new ConnectionException("Failed creating a client");
55+
56+
if (client._clientPointer != IntPtr.Zero)
57+
{
58+
// Initialize server version after successful connection
59+
await client.InitializeServerVersionAsync();
60+
return client;
61+
}
62+
63+
throw new ConnectionException("Failed creating a client");
5864
}
5965

6066
protected BaseClient()
@@ -139,6 +145,8 @@ private void FailureCallback(ulong index, IntPtr strPtr, RequestErrorType errTyp
139145

140146
internal void SetInfo(string info) => _clientInfo = info;
141147

148+
protected abstract Task InitializeServerVersionAsync();
149+
142150
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
143151
private delegate void SuccessAction(ulong index, IntPtr ptr);
144152
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
@@ -160,6 +168,7 @@ private void FailureCallback(ulong index, IntPtr strPtr, RequestErrorType errTyp
160168
private readonly MessageContainer _messageContainer;
161169
private readonly object _lock = new();
162170
private string _clientInfo = ""; // used to distinguish and identify clients during tests
171+
protected Version? _serverVersion; // cached server version
163172

164173
#endregion private fields
165174
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,14 @@ public static class Constants
7777
/// The lowest bound in the sorted set for score operations.
7878
/// </summary>
7979
public const string NegativeInfinityScore = "-inf";
80+
81+
/// <summary>
82+
/// Keywords for SORT command.
83+
/// </summary>
84+
public const string AlphaKeyword = "ALPHA";
85+
public const string AscKeyword = "ASC";
86+
public const string DescKeyword = "DESC";
87+
public const string ByKeyword = "BY";
88+
public const string GetKeyword = "GET";
89+
public const string StoreKeyword = "STORE";
8090
}

sources/Valkey.Glide/Commands/IGenericBaseCommands.cs

Lines changed: 179 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,26 @@ public interface IGenericBaseCommands
127127
/// </remarks>
128128
Task<long> KeyExistsAsync(ValkeyKey[] keys, CommandFlags flags = CommandFlags.None);
129129

130+
/// <summary>
131+
/// Set a timeout on key. After the timeout has expired, the key will automatically be deleted.<br/>
132+
/// If key already has an existing expire set, the time to live is updated to the new value.
133+
/// If expiry is a non-positive value, the key will be deleted rather than expired.
134+
/// The timeout will only be cleared by commands that delete or overwrite the contents of key.
135+
/// </summary>
136+
/// <seealso href="https://valkey.io/commands/expire"/>
137+
/// <param name="key">The key to expire.</param>
138+
/// <param name="expiry">Duration for the key to expire.</param>
139+
/// <param name="flags">The flags to use for this operation. Currently flags are ignored.</param>
140+
/// <returns><see langword="true"/> if the timeout was set. <see langword="false"/> if key does not exist or the timeout could not be set.</returns>
141+
/// <remarks>
142+
/// <example>
143+
/// <code>
144+
/// bool result = await client.KeyExpireAsync(key, TimeSpan.FromSeconds(10));
145+
/// </code>
146+
/// </example>
147+
/// </remarks>
148+
Task<bool> KeyExpireAsync(ValkeyKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None);
149+
130150
/// <summary>
131151
/// Set a timeout on key. After the timeout has expired, the key will automatically be deleted.<br/>
132152
/// If key already has an existing expire set, the time to live is updated to the new value.
@@ -146,16 +166,34 @@ public interface IGenericBaseCommands
146166
/// </code>
147167
/// </example>
148168
/// </remarks>
149-
Task<bool> KeyExpireAsync(ValkeyKey key, TimeSpan? expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None);
169+
Task<bool> KeyExpireAsync(ValkeyKey key, TimeSpan? expiry, ExpireWhen when, CommandFlags flags = CommandFlags.None);
150170

151171
/// <summary>
152172
/// Sets a timeout on key. It takes an absolute Unix timestamp (seconds since January 1, 1970) instead of
153173
/// specifying the duration. A timestamp in the past will delete the key immediately. After the timeout has
154174
/// expired, the key will automatically be deleted.<br/>
155175
/// If key already has an existing expire set, the time to live is updated to the new value.
156-
/// The timeout will only be cleared by commands that delete or overwrite the contents of key
176+
/// The timeout will only be cleared by commands that delete or overwrite the contents of key.
177+
/// </summary>
178+
/// <seealso href="https://valkey.io/commands/expireat"/>
179+
/// <param name="key">The key to expire.</param>
180+
/// <param name="expiry">The timestamp for expiry.</param>
181+
/// <param name="flags">The flags to use for this operation. Currently flags are ignored.</param>
182+
/// <returns><see langword="true"/> if the timeout was set. <see langword="false"/> if key does not exist or the timeout could not be set.</returns>
183+
/// <remarks>
184+
/// <example>
185+
/// <code>
186+
/// bool result = await client.KeyExpireAsync(key, DateTime.UtcNow.AddMinutes(5));
187+
/// </code>
188+
/// </example>
189+
/// </remarks>
190+
Task<bool> KeyExpireAsync(ValkeyKey key, DateTime? expiry, CommandFlags flags = CommandFlags.None);
191+
192+
/// <summary>
193+
/// Sets a timeout on key. It takes an absolute Unix timestamp (seconds since January 1, 1970) instead of
194+
/// specifying the duration. A timestamp in the past will delete the key immediately. After the timeout has
195+
/// expired, the key will automatically be deleted.<br/>
157196
/// If key already has an existing expire set, the time to live is updated to the new value.
158-
/// If expiry is a non-positive value, the key will be deleted rather than expired.
159197
/// The timeout will only be cleared by commands that delete or overwrite the contents of key.
160198
/// </summary>
161199
/// <seealso href="https://valkey.io/commands/expireat"/>
@@ -171,7 +209,7 @@ public interface IGenericBaseCommands
171209
/// </code>
172210
/// </example>
173211
/// </remarks>
174-
Task<bool> KeyExpireAsync(ValkeyKey key, DateTime? expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None);
212+
Task<bool> KeyExpireAsync(ValkeyKey key, DateTime? expiry, ExpireWhen when, CommandFlags flags = CommandFlags.None);
175213

176214
/// <summary>
177215
/// Returns the remaining time to live of a key that has a timeout.
@@ -359,6 +397,86 @@ public interface IGenericBaseCommands
359397
/// </remarks>
360398
Task<long> KeyTouchAsync(ValkeyKey[] keys, CommandFlags flags = CommandFlags.None);
361399

400+
/// <summary>
401+
/// Returns the absolute time at which the given key will expire.
402+
/// </summary>
403+
/// <seealso href="https://valkey.io/commands/pexpiretime"/>
404+
/// <param name="key">The key to determine the expiration value of.</param>
405+
/// <param name="flags">The flags to use for this operation. Currently flags are ignored.</param>
406+
/// <returns>The expiration time, or <see langword="null"/> when key does not exist or key exists but has no associated expiration.</returns>
407+
/// <remarks>
408+
/// <example>
409+
/// <code>
410+
/// DateTime? expiration = await client.KeyExpireTimeAsync(key);
411+
/// </code>
412+
/// </example>
413+
/// </remarks>
414+
Task<DateTime?> KeyExpireTimeAsync(ValkeyKey key, CommandFlags flags = CommandFlags.None);
415+
416+
/// <summary>
417+
/// Returns the internal encoding for the object stored at key.
418+
/// </summary>
419+
/// <seealso href="https://valkey.io/commands/object-encoding"/>
420+
/// <param name="key">The key to determine the encoding of.</param>
421+
/// <param name="flags">The flags to use for this operation. Currently flags are ignored.</param>
422+
/// <returns>The encoding of the object, or <see langword="null"/> when key does not exist.</returns>
423+
/// <remarks>
424+
/// <example>
425+
/// <code>
426+
/// string? encoding = await client.KeyEncodingAsync(key);
427+
/// </code>
428+
/// </example>
429+
/// </remarks>
430+
Task<string?> KeyEncodingAsync(ValkeyKey key, CommandFlags flags = CommandFlags.None);
431+
432+
/// <summary>
433+
/// Returns the logarithmic access frequency counter for the object stored at key.
434+
/// </summary>
435+
/// <seealso href="https://valkey.io/commands/object-freq"/>
436+
/// <param name="key">The key to determine the frequency of.</param>
437+
/// <param name="flags">The flags to use for this operation. Currently flags are ignored.</param>
438+
/// <returns>The frequency counter, or <see langword="null"/> when key does not exist.</returns>
439+
/// <remarks>
440+
/// <example>
441+
/// <code>
442+
/// long? frequency = await client.KeyFrequencyAsync(key);
443+
/// </code>
444+
/// </example>
445+
/// </remarks>
446+
Task<long?> KeyFrequencyAsync(ValkeyKey key, CommandFlags flags = CommandFlags.None);
447+
448+
/// <summary>
449+
/// Returns the time in seconds since the object stored at key was last accessed.
450+
/// </summary>
451+
/// <seealso href="https://valkey.io/commands/object-idletime"/>
452+
/// <param name="key">The key to determine the idle time of.</param>
453+
/// <param name="flags">The flags to use for this operation. Currently flags are ignored.</param>
454+
/// <returns>The idle time in seconds, or <see langword="null"/> when key does not exist.</returns>
455+
/// <remarks>
456+
/// <example>
457+
/// <code>
458+
/// long? idleTime = await client.KeyIdleTimeAsync(key);
459+
/// </code>
460+
/// </example>
461+
/// </remarks>
462+
Task<long?> KeyIdleTimeAsync(ValkeyKey key, CommandFlags flags = CommandFlags.None);
463+
464+
/// <summary>
465+
/// Returns the reference count of the object stored at key.
466+
/// </summary>
467+
/// <seealso href="https://valkey.io/commands/object-refcount"/>
468+
/// <param name="key">The key to determine the reference count of.</param>
469+
/// <param name="flags">The flags to use for this operation. Currently flags are ignored.</param>
470+
/// <returns>The reference count, or <see langword="null"/> when key does not exist.</returns>
471+
/// <remarks>
472+
/// <example>
473+
/// <code>
474+
/// long? refCount = await client.KeyRefCountAsync(key);
475+
/// </code>
476+
/// </example>
477+
/// </remarks>
478+
Task<long?> KeyRefCountAsync(ValkeyKey key, CommandFlags flags = CommandFlags.None);
479+
362480
/// <summary>
363481
/// Copies the value stored at the source to the destination key. When
364482
/// replace is true, removes the destination key first if it already
@@ -380,4 +498,61 @@ public interface IGenericBaseCommands
380498
/// </example>
381499
/// </remarks>
382500
Task<bool> KeyCopyAsync(ValkeyKey sourceKey, ValkeyKey destinationKey, bool replace = false, CommandFlags flags = CommandFlags.None);
501+
502+
/// <summary>
503+
/// Returns a random key from the database.
504+
/// </summary>
505+
/// <seealso href="https://valkey.io/commands/randomkey"/>
506+
/// <param name="flags">The flags to use for this operation. Currently flags are ignored.</param>
507+
/// <returns>A random key, or <see langword="null"/> when the database is empty.</returns>
508+
/// <remarks>
509+
/// <example>
510+
/// <code>
511+
/// string? randomKey = await client.KeyRandomAsync();
512+
/// </code>
513+
/// </example>
514+
/// </remarks>
515+
Task<string?> KeyRandomAsync(CommandFlags flags = CommandFlags.None);
516+
517+
/// <summary>
518+
/// Sorts the elements in the list, set, or sorted set at key and returns the result.
519+
/// </summary>
520+
/// <seealso href="https://valkey.io/commands/sort"/>
521+
/// <param name="key">The key of the list, set, or sorted set to be sorted.</param>
522+
/// <param name="skip">The number of elements to skip.</param>
523+
/// <param name="take">The number of elements to take. -1 means take all.</param>
524+
/// <param name="order">The sort order.</param>
525+
/// <param name="sortType">The sort type.</param>
526+
/// <param name="by">The pattern to sort by external keys.</param>
527+
/// <param name="get">The patterns to retrieve external keys' values.</param>
528+
/// <param name="flags">The flags to use for this operation. Currently flags are ignored.</param>
529+
/// <returns>An array of sorted elements.</returns>
530+
/// <remarks>
531+
/// <example>
532+
/// <code>
533+
/// await client.ListLeftPushAsync("mylist", ["3", "1", "2"]);
534+
/// ValkeyValue[] result = await client.SortAsync("mylist");
535+
/// // result is ["1", "2", "3"]
536+
/// </code>
537+
/// </example>
538+
/// </remarks>
539+
Task<ValkeyValue[]> SortAsync(ValkeyKey key, long skip = 0, long take = -1, Order order = Order.Ascending, SortType sortType = SortType.Numeric, ValkeyValue by = default, ValkeyValue[]? get = null, CommandFlags flags = CommandFlags.None);
540+
541+
/// <summary>
542+
/// Blocks the current client until all the previous write commands are successfully transferred and acknowledged by at least numreplicas replicas.
543+
/// If the timeout is reached, the command returns even if the specified number of replicas were not yet reached.
544+
/// </summary>
545+
/// <seealso href="https://valkey.io/commands/wait"/>
546+
/// <param name="numreplicas">The number of replicas to wait for.</param>
547+
/// <param name="timeout">The timeout in milliseconds.</param>
548+
/// <param name="flags">The flags to use for this operation. Currently flags are ignored.</param>
549+
/// <returns>The number of replicas that acknowledged the write commands.</returns>
550+
/// <remarks>
551+
/// <example>
552+
/// <code>
553+
/// long result = await client.WaitAsync(1, 1000);
554+
/// </code>
555+
/// </example>
556+
/// </remarks>
557+
Task<long> WaitAsync(long numreplicas, long timeout, CommandFlags flags = CommandFlags.None);
383558
}

0 commit comments

Comments
 (0)