Skip to content

Commit 94c20e3

Browse files
committed
Merge branch 'main' of https://github.com/valkey-io/valkey-glide-csharp into currantw/search-commands-refactor
Signed-off-by: currantw <taylor.curran@improving.com>
2 parents 7b683c2 + e95daf0 commit 94c20e3

14 files changed

Lines changed: 5558 additions & 4 deletions

scripts/validate_examples.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class ExamplesValidator:
6060
"Valkey.Glide.Commands.Options.InfoOptions",
6161
"Valkey.Glide.Commands.Options.BitFieldOptions",
6262
"Valkey.Glide.Commands.Options.BitFieldOptions.Encoding",
63+
"Valkey.Glide.ServerModules.GlideJson",
6364
]
6465

6566
# Client fields to include when --add-clients is specified.

sources/Valkey.Glide/Client/IBaseClient.StringCommands.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ public partial interface IBaseClient
273273
/// <remarks>
274274
/// <example>
275275
/// <code>
276-
/// var wasSet = await client.SetAsync("key", "value", SetCondition.OnlyIfDoesNotExist); // true
276+
/// var wasSet = await client.SetAsync("key", "value", Valkey.Glide.Commands.Options.SetCondition.OnlyIfDoesNotExist); // true
277277
/// </code>
278278
/// </example>
279279
/// </remarks>
@@ -290,7 +290,7 @@ public partial interface IBaseClient
290290
/// <remarks>
291291
/// <example>
292292
/// <code>
293-
/// var setOptions = new SetOptions { Condition = SetCondition.OnlyIfDoesNotExist };
293+
/// var setOptions = new SetOptions { Condition = Valkey.Glide.Commands.Options.SetCondition.OnlyIfDoesNotExist };
294294
/// var wasSet = await client.SetAsync("key", "value", setOptions); // true
295295
/// </code>
296296
/// </example>
@@ -342,7 +342,7 @@ public partial interface IBaseClient
342342
/// <example>
343343
/// <code>
344344
/// await client.SetAsync("key", "oldValue");
345-
/// var previous = await client.GetSetAsync("key", "newValue", SetCondition.OnlyIfExists); // "oldValue"
345+
/// var previous = await client.GetSetAsync("key", "newValue", Valkey.Glide.Commands.Options.SetCondition.OnlyIfExists); // "oldValue"
346346
/// </code>
347347
/// </example>
348348
/// </remarks>
@@ -360,7 +360,7 @@ public partial interface IBaseClient
360360
/// <example>
361361
/// <code>
362362
/// await client.SetAsync("key", "oldValue");
363-
/// var setOptions = new SetOptions { Condition = SetCondition.OnlyIfExists };
363+
/// var setOptions = new SetOptions { Condition = Valkey.Glide.Commands.Options.SetCondition.OnlyIfExists };
364364
/// var previous = await client.GetSetAsync("key", "newValue", setOptions); // "oldValue"
365365
/// </code>
366366
/// </example>
Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
2+
3+
namespace Valkey.Glide.ServerModules;
4+
5+
/// <summary>
6+
/// Module for JSON commands - Array operations.
7+
/// </summary>
8+
public static partial class GlideJson
9+
{
10+
#region JSON.ARRAPPEND
11+
12+
/// <summary>
13+
/// Appends one or more values to the array at the specified path in the JSON document stored at the key.
14+
/// </summary>
15+
/// <param name="client">The Glide client to use for the command.</param>
16+
/// <param name="key">The key where the JSON document is stored.</param>
17+
/// <param name="path">The JSONPath or legacy path within the JSON document.</param>
18+
/// <param name="values">The JSON values to append to the array.</param>
19+
/// <returns>
20+
/// An array of new array lengths for each matching path.
21+
/// Elements are <see langword="null"/> for paths where the value is not an array.
22+
/// </returns>
23+
/// <exception cref="Exception">Thrown if the key does not exist.</exception>
24+
/// <seealso href="https://valkey.io/commands/json.arrappend/">Valkey commands – JSON.ARRAPPEND</seealso>
25+
/// <remarks>
26+
/// <example>
27+
/// <code>
28+
/// await GlideJson.SetAsync(client, "mykey", "$", "{\"arr\":[1,2],\"str\":\"hello\"}");
29+
/// var lengths = await GlideJson.ArrAppendAsync(client, "mykey", "$.*", ["3"]);
30+
/// // lengths = [3, null] - arr now has 3 elements, str is not an array (null)
31+
/// </code>
32+
/// </example>
33+
/// </remarks>
34+
public static async Task<long?[]> ArrAppendAsync(BaseClient client, ValkeyKey key, ValkeyValue path, IEnumerable<ValkeyValue> values)
35+
{
36+
GlideString[] args = BuildArrAppendArgs(ToGlideString(key), ToGlideString(path), [.. values.Select(v => ToGlideString(v))]);
37+
object? result = await ExecuteCommandAsync(client, args);
38+
return ConvertToNullableLongArrayNonNull(result);
39+
}
40+
41+
private static GlideString[] BuildArrAppendArgs(GlideString key, GlideString path, GlideString[] values)
42+
{
43+
List<GlideString> args = [JsonArrAppend, key, path];
44+
args.AddRange(values);
45+
return [.. args];
46+
}
47+
48+
#endregion
49+
50+
#region JSON.ARRINSERT
51+
52+
/// <summary>
53+
/// Inserts one or more values into the array at the specified path and index in the JSON document stored at the key.
54+
/// </summary>
55+
/// <param name="client">The Glide client to use for the command.</param>
56+
/// <param name="key">The key where the JSON document is stored.</param>
57+
/// <param name="path">The JSONPath or legacy path within the JSON document.</param>
58+
/// <param name="index">The index before which to insert the values. Negative indices count from the end.</param>
59+
/// <param name="values">The JSON values to insert into the array.</param>
60+
/// <returns>
61+
/// An array of new array lengths for each matching path.
62+
/// Elements are <see langword="null"/> for paths where the value is not an array.
63+
/// </returns>
64+
/// <exception cref="Exception">Thrown if the key does not exist.</exception>
65+
/// <seealso href="https://valkey.io/commands/json.arrinsert/">Valkey commands – JSON.ARRINSERT</seealso>
66+
/// <remarks>
67+
/// <example>
68+
/// <code>
69+
/// await GlideJson.SetAsync(client, "mykey", "$", "{\"arr\":[1,3],\"str\":\"hello\"}");
70+
/// var lengths = await GlideJson.ArrInsertAsync(client, "mykey", "$.*", 1, ["2"]);
71+
/// // lengths = [3, null] - arr now has 3 elements, str is not an array (null)
72+
/// </code>
73+
/// </example>
74+
/// </remarks>
75+
public static async Task<long?[]> ArrInsertAsync(BaseClient client, ValkeyKey key, ValkeyValue path, long index, IEnumerable<ValkeyValue> values)
76+
{
77+
GlideString[] args = BuildArrInsertArgs(ToGlideString(key), ToGlideString(path), index, [.. values.Select(v => ToGlideString(v))]);
78+
object? result = await ExecuteCommandAsync(client, args);
79+
return ConvertToNullableLongArrayNonNull(result);
80+
}
81+
82+
private static GlideString[] BuildArrInsertArgs(GlideString key, GlideString path, long index, GlideString[] values)
83+
{
84+
List<GlideString> args = [JsonArrInsert, key, path, index.ToString()];
85+
args.AddRange(values);
86+
return [.. args];
87+
}
88+
89+
#endregion
90+
91+
#region JSON.ARRINDEX
92+
93+
/// <summary>
94+
/// Searches for the first occurrence of a value in the array at the specified path.
95+
/// </summary>
96+
/// <param name="client">The Glide client to use for the command.</param>
97+
/// <param name="key">The key where the JSON document is stored.</param>
98+
/// <param name="path">The JSONPath or legacy path within the JSON document.</param>
99+
/// <param name="value">The JSON value to search for.</param>
100+
/// <returns>
101+
/// An array of indices for each matching path, or <see langword="null"/> if the key does not exist.
102+
/// Elements are -1 if not found, or <see langword="null"/> for paths where the value is not an array.
103+
/// </returns>
104+
/// <seealso href="https://valkey.io/commands/json.arrindex/">Valkey commands – JSON.ARRINDEX</seealso>
105+
/// <remarks>
106+
/// <example>
107+
/// <code>
108+
/// await GlideJson.SetAsync(client, "mykey", "$", "{\"arr\":[1,2,3],\"str\":\"hello\"}");
109+
/// var indices = await GlideJson.ArrIndexAsync(client, "mykey", "$.*", "2");
110+
/// // indices = [1, null] - found at index 1 in arr, str is not an array (null)
111+
///
112+
/// var missing = await GlideJson.ArrIndexAsync(client, "nonexistent", "$.*", "2");
113+
/// // missing = null - key doesn't exist
114+
/// </code>
115+
/// </example>
116+
/// </remarks>
117+
public static async Task<long?[]?> ArrIndexAsync(BaseClient client, ValkeyKey key, ValkeyValue path, ValkeyValue value)
118+
{
119+
GlideString[] args = [JsonArrIndex, ToGlideString(key), ToGlideString(path), ToGlideString(value)];
120+
object? result = await ExecuteCommandAsync(client, args);
121+
return ConvertToNullableLongArray(result);
122+
}
123+
124+
/// <summary>
125+
/// Searches for the first occurrence of a value in the array at the specified path with range options.
126+
/// </summary>
127+
/// <param name="client">The Glide client to use for the command.</param>
128+
/// <param name="key">The key where the JSON document is stored.</param>
129+
/// <param name="path">The JSONPath or legacy path within the JSON document.</param>
130+
/// <param name="value">The JSON value to search for.</param>
131+
/// <param name="range">Range options for the search.</param>
132+
/// <returns>
133+
/// An array of indices for each matching path, or <see langword="null"/> if the key does not exist.
134+
/// Elements are -1 if not found, or <see langword="null"/> for paths where the value is not an array.
135+
/// </returns>
136+
/// <seealso href="https://valkey.io/commands/json.arrindex/">Valkey commands – JSON.ARRINDEX</seealso>
137+
/// <remarks>
138+
/// <example>
139+
/// <code>
140+
/// await GlideJson.SetAsync(client, "mykey", "$", "{\"arr\":[1,2,3,2]}");
141+
/// var range = GlideJson.ArrIndexRange.Between(2, 4);
142+
/// var indices = await GlideJson.ArrIndexAsync(client, "mykey", "$.arr", "2", range); // [3]
143+
/// </code>
144+
/// </example>
145+
/// </remarks>
146+
public static async Task<long?[]?> ArrIndexAsync(BaseClient client, ValkeyKey key, ValkeyValue path, ValkeyValue value, ArrIndexRange range)
147+
{
148+
GlideString[] args = BuildArrIndexArgs(ToGlideString(key), ToGlideString(path), ToGlideString(value), range);
149+
object? result = await ExecuteCommandAsync(client, args);
150+
return ConvertToNullableLongArray(result);
151+
}
152+
153+
private static GlideString[] BuildArrIndexArgs(GlideString key, GlideString path, GlideString value, ArrIndexRange range)
154+
{
155+
List<GlideString> args = [JsonArrIndex, key, path, value];
156+
args.AddRange(range.ToArgs());
157+
return [.. args];
158+
}
159+
160+
#endregion
161+
162+
#region JSON.ARRLEN
163+
164+
/// <summary>
165+
/// Gets the length of the array at the specified path in the JSON document stored at the key.
166+
/// </summary>
167+
/// <param name="client">The Glide client to use for the command.</param>
168+
/// <param name="key">The key where the JSON document is stored.</param>
169+
/// <param name="path">The JSONPath or legacy path within the JSON document.</param>
170+
/// <returns>
171+
/// An array of lengths for each matching path, or <see langword="null"/> if the key does not exist.
172+
/// Elements are <see langword="null"/> for paths where the value is not an array.
173+
/// </returns>
174+
/// <seealso href="https://valkey.io/commands/json.arrlen/">Valkey commands – JSON.ARRLEN</seealso>
175+
/// <remarks>
176+
/// <example>
177+
/// <code>
178+
/// await GlideJson.SetAsync(client, "mykey", "$", "{\"arr\":[1,2,3],\"str\":\"hello\"}");
179+
/// var lengths = await GlideJson.ArrLenAsync(client, "mykey", "$.*");
180+
/// // lengths = [3, null] - arr has 3 elements, str is not an array (null)
181+
///
182+
/// var missing = await GlideJson.ArrLenAsync(client, "nonexistent", "$.*");
183+
/// // missing = null - key doesn't exist
184+
/// </code>
185+
/// </example>
186+
/// </remarks>
187+
public static async Task<long?[]?> ArrLenAsync(BaseClient client, ValkeyKey key, ValkeyValue path)
188+
{
189+
GlideString[] args = [JsonArrLen, ToGlideString(key), ToGlideString(path)];
190+
object? result = await ExecuteCommandAsync(client, args);
191+
return ConvertToNullableLongArray(result);
192+
}
193+
194+
/// <summary>
195+
/// Gets the length of the array at the root path in the JSON document stored at the key.
196+
/// </summary>
197+
/// <param name="client">The Glide client to use for the command.</param>
198+
/// <param name="key">The key where the JSON document is stored.</param>
199+
/// <returns>The array length at the root path, or <see langword="null"/> if the key does not exist or root is not an array.</returns>
200+
/// <seealso href="https://valkey.io/commands/json.arrlen/">Valkey commands – JSON.ARRLEN</seealso>
201+
/// <remarks>
202+
/// <example>
203+
/// <code>
204+
/// await GlideJson.SetAsync(client, "mykey", "$", "[1,2,3]");
205+
/// var length = await GlideJson.ArrLenAsync(client, "mykey"); // 3
206+
/// </code>
207+
/// </example>
208+
/// </remarks>
209+
public static async Task<long?> ArrLenAsync(BaseClient client, ValkeyKey key)
210+
{
211+
GlideString[] args = [JsonArrLen, ToGlideString(key)];
212+
object? result = await ExecuteCommandAsync(client, args);
213+
return result is null ? null : (long)result;
214+
}
215+
216+
#endregion
217+
218+
#region JSON.ARRPOP
219+
220+
/// <summary>
221+
/// Pops an element from the array at the specified path and index in the JSON document stored at the key.
222+
/// </summary>
223+
/// <param name="client">The Glide client to use for the command.</param>
224+
/// <param name="key">The key where the JSON document is stored.</param>
225+
/// <param name="path">The JSONPath or legacy path within the JSON document.</param>
226+
/// <param name="index">The index of the element to pop. Negative indices count from the end. Default is -1 (last element).</param>
227+
/// <returns>
228+
/// An array of popped elements (as JSON strings) for each matching path. Returns <see langword="null"/> for non-array/empty matches.
229+
/// </returns>
230+
/// <seealso href="https://valkey.io/commands/json.arrpop/">Valkey commands – JSON.ARRPOP</seealso>
231+
/// <remarks>
232+
/// <example>
233+
/// <code>
234+
/// await GlideJson.SetAsync(client, "mykey", "$", "{\"arr\":[1,2,3]}");
235+
/// var popped = await GlideJson.ArrPopAsync(client, "mykey", "$.arr", -1); // ["3"]
236+
/// </code>
237+
/// </example>
238+
/// </remarks>
239+
public static async Task<ValkeyValue?[]?> ArrPopAsync(BaseClient client, ValkeyKey key, ValkeyValue path, long index = -1)
240+
{
241+
GlideString[] args = [JsonArrPop, ToGlideString(key), ToGlideString(path), index.ToString()];
242+
object? result = await ExecuteCommandAsync(client, args);
243+
return ConvertToNullableValkeyValueArray(result);
244+
}
245+
246+
private static ValkeyValue?[]? ConvertToNullableValkeyValueArray(object? result)
247+
{
248+
if (result is null)
249+
return null;
250+
if (result is object?[] arr)
251+
return [.. arr.Select(o => o is null ? (ValkeyValue?)null : ToValkeyValue(o))];
252+
// Single value (legacy path) - wrap in array for consistent return type
253+
return [ToValkeyValue(result)];
254+
}
255+
256+
/// <summary>
257+
/// Pops the last element from the array at the root path in the JSON document stored at the key.
258+
/// </summary>
259+
/// <param name="client">The Glide client to use for the command.</param>
260+
/// <param name="key">The key where the JSON document is stored.</param>
261+
/// <returns>The popped element as a JSON string, or <see cref="ValkeyValue.Null"/> if the array is empty or key does not exist.</returns>
262+
/// <seealso href="https://valkey.io/commands/json.arrpop/">Valkey commands – JSON.ARRPOP</seealso>
263+
/// <remarks>
264+
/// <example>
265+
/// <code>
266+
/// await GlideJson.SetAsync(client, "mykey", "$", "[1,2,3]");
267+
/// var popped = await GlideJson.ArrPopAsync(client, "mykey"); // "3"
268+
/// </code>
269+
/// </example>
270+
/// </remarks>
271+
public static async Task<ValkeyValue> ArrPopAsync(BaseClient client, ValkeyKey key)
272+
{
273+
GlideString[] args = [JsonArrPop, ToGlideString(key)];
274+
object? result = await ExecuteCommandAsync(client, args);
275+
return ToValkeyValue(result);
276+
}
277+
278+
#endregion
279+
280+
#region JSON.ARRTRIM
281+
282+
/// <summary>
283+
/// Trims the array at the specified path to contain only elements within the specified range.
284+
/// </summary>
285+
/// <param name="client">The Glide client to use for the command.</param>
286+
/// <param name="key">The key where the JSON document is stored.</param>
287+
/// <param name="path">The JSONPath or legacy path within the JSON document.</param>
288+
/// <param name="start">The start index (inclusive).</param>
289+
/// <param name="end">The end index (inclusive). Negative indices count from the end.</param>
290+
/// <returns>
291+
/// An array of new array lengths for each matching path.
292+
/// Elements are <see langword="null"/> for paths where the value is not an array.
293+
/// </returns>
294+
/// <exception cref="Exception">Thrown if the key does not exist.</exception>
295+
/// <seealso href="https://valkey.io/commands/json.arrtrim/">Valkey commands – JSON.ARRTRIM</seealso>
296+
/// <remarks>
297+
/// <example>
298+
/// <code>
299+
/// await GlideJson.SetAsync(client, "mykey", "$", "{\"arr\":[1,2,3,4,5],\"str\":\"hello\"}");
300+
/// var lengths = await GlideJson.ArrTrimAsync(client, "mykey", "$.*", 1, 3);
301+
/// // lengths = [3, null] - arr now has 3 elements [2,3,4], str is not an array (null)
302+
/// </code>
303+
/// </example>
304+
/// </remarks>
305+
public static async Task<long?[]> ArrTrimAsync(BaseClient client, ValkeyKey key, ValkeyValue path, long start, long end)
306+
{
307+
GlideString[] args = [JsonArrTrim, ToGlideString(key), ToGlideString(path), start.ToString(), end.ToString()];
308+
object? result = await ExecuteCommandAsync(client, args);
309+
return ConvertToNullableLongArrayNonNull(result);
310+
}
311+
312+
#endregion
313+
}

0 commit comments

Comments
 (0)