Skip to content

Commit fffb5c1

Browse files
LPOS feature (#2080)
Implements the [LPOS](https://redis.io/commands/lpos/) command for #2055 Co-authored-by: Nick Craver <[email protected]>
1 parent cd33beb commit fffb5c1

File tree

14 files changed

+705
-3
lines changed

14 files changed

+705
-3
lines changed

docs/ReleaseNotes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- Adds: Support for `ZRANDMEMBER` with `.SortedSetRandomMember()`/`.SortedSetRandomMemberAsync()`, `.SortedSetRandomMembers()`/`.SortedSetRandomMembersAsync()`, and `.SortedSetRandomMembersWithScores()`/`.SortedSetRandomMembersWithScoresAsync()` ([#2076 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2076))
1313
- Adds: Support for `SMISMEMBER` with `.SetContains()`/`.SetContainsAsync()` ([#2077 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2077))
1414
- Adds: Support for `SINTERCARD` with `.SetIntersectionLength()`/`.SetIntersectionLengthAsync()` ([#2078 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2078))
15+
- Adds: Support for `LPOS` with `.ListPosition()`/`.ListPositionAsync()` and `.ListPositions()`/`.ListPositionsAsync()` ([#2080 by slorello89](https://github.com/StackExchange/StackExchange.Redis/pull/2080))
1516

1617
## 2.5.61
1718

src/StackExchange.Redis/Enums/RedisCommand.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ internal enum RedisCommand
8585
LLEN,
8686
LMOVE,
8787
LPOP,
88+
LPOS,
8889
LPUSH,
8990
LPUSHX,
9091
LRANGE,

src/StackExchange.Redis/Interfaces/IDatabase.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,31 @@ public interface IDatabase : IRedis, IDatabaseAsync
721721
/// <remarks>https://redis.io/commands/lpop</remarks>
722722
RedisValue[] ListLeftPop(RedisKey key, long count, CommandFlags flags = CommandFlags.None);
723723

724+
/// <summary>
725+
/// Scans through the list stored at <paramref name="key"/> looking for <paramref name="element"/>, returning the 0-based
726+
/// index of the first matching element.
727+
/// </summary>
728+
/// <param name="key">The key of the list.</param>
729+
/// <param name="element">The element to search for.</param>
730+
/// <param name="rank">The rank of the first element to return, within the sub-list of matching indexes in the case of multiple matches.</param>
731+
/// <param name="maxLength">The maximum number of elements to scan through before stopping, defaults to 0 (a full scan of the list.)</param>
732+
/// <param name="flags">The flags to use for this operation.</param>
733+
/// <returns>The 0-based index of the first matching element, or -1 if not found.</returns>
734+
long ListPosition(RedisKey key, RedisValue element, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None);
735+
736+
/// <summary>
737+
/// Scans through the list stored at <paramref name="key"/> looking for <paramref name="count"/> instances of <paramref name="element"/>, returning the 0-based
738+
/// indexes of any matching elements.
739+
/// </summary>
740+
/// <param name="key">The key of the list.</param>
741+
/// <param name="element">The element to search for.</param>
742+
/// <param name="count">The number of matches to find. A count of 0 will return the indexes of all occurrences of the element.</param>
743+
/// <param name="rank">The rank of the first element to return, within the sub-list of matching indexes in the case of multiple matches.</param>
744+
/// <param name="maxLength">The maximum number of elements to scan through before stopping, defaults to 0 (a full scan of the list.)</param>
745+
/// <param name="flags">The flags to use for this operation.</param>
746+
/// <returns>An array of at most <paramref name="count"/> of indexes of matching elements. If none are found, and empty array is returned.</returns>
747+
long[] ListPositions(RedisKey key, RedisValue element, long count, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None);
748+
724749
/// <summary>
725750
/// Insert the specified value at the head of the list stored at key.
726751
/// If key does not exist, it is created as empty list before performing the push operations.

src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,31 @@ public interface IDatabaseAsync : IRedisAsync
697697
/// <remarks>https://redis.io/commands/lpop</remarks>
698698
Task<RedisValue[]> ListLeftPopAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None);
699699

700+
/// <summary>
701+
/// Scans through the list stored at <paramref name="key"/> looking for <paramref name="element"/>, returning the 0-based
702+
/// index of the first matching element.
703+
/// </summary>
704+
/// <param name="key">The key of the list.</param>
705+
/// <param name="element">The element to search for.</param>
706+
/// <param name="rank">The rank of the first element to return, within the sub-list of matching indexes in the case of multiple matches.</param>
707+
/// <param name="maxLength">The maximum number of elements to scan through before stopping, defaults to 0 (a full scan of the list.)</param>
708+
/// <param name="flags">The flags to use for this operation.</param>
709+
/// <returns>The 0-based index of the first matching element, or -1 if not found.</returns>
710+
Task<long> ListPositionAsync(RedisKey key, RedisValue element, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None);
711+
712+
/// <summary>
713+
/// Scans through the list stored at <paramref name="key"/> looking for <paramref name="count"/> instances of <paramref name="element"/>, returning the 0-based
714+
/// indexes of any matching elements.
715+
/// </summary>
716+
/// <param name="key">The key of the list.</param>
717+
/// <param name="element">The element to search for.</param>
718+
/// <param name="count">The number of matches to find. A count of 0 will return the indexes of all occurrences of the element.</param>
719+
/// <param name="rank">The rank of the first element to return, within the sub-list of matching indexes in the case of multiple matches.</param>
720+
/// <param name="maxLength">The maximum number of elements to scan through before stopping, defaults to 0 (a full scan of the list.)</param>
721+
/// <param name="flags">The flags to use for this operation.</param>
722+
/// <returns>An array of at most <paramref name="count"/> of indexes of matching elements. If none are found, and empty array is returned.</returns>
723+
Task<long[]> ListPositionsAsync(RedisKey key, RedisValue element, long count, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None);
724+
700725
/// <summary>
701726
/// Insert the specified value at the head of the list stored at key.
702727
/// If key does not exist, it is created as empty list before performing the push operations.

src/StackExchange.Redis/KeyspaceIsolation/DatabaseWrapper.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,12 @@ public RedisValue ListLeftPop(RedisKey key, CommandFlags flags = CommandFlags.No
191191
public RedisValue[] ListLeftPop(RedisKey key, long count, CommandFlags flags = CommandFlags.None) =>
192192
Inner.ListLeftPop(ToInner(key), count, flags);
193193

194+
public long ListPosition(RedisKey key, RedisValue element, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None) =>
195+
Inner.ListPosition(ToInner(key), element, rank, maxLength, flags);
196+
197+
public long[] ListPositions(RedisKey key, RedisValue element, long count, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None) =>
198+
Inner.ListPositions(ToInner(key), element, count, rank, maxLength, flags);
199+
194200
public long ListLeftPush(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) =>
195201
Inner.ListLeftPush(ToInner(key), values, flags);
196202

src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,12 @@ public Task<RedisValue> ListLeftPopAsync(RedisKey key, CommandFlags flags = Comm
201201
public Task<RedisValue[]> ListLeftPopAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) =>
202202
Inner.ListLeftPopAsync(ToInner(key), count, flags);
203203

204+
public Task<long> ListPositionAsync(RedisKey key, RedisValue element, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None) =>
205+
Inner.ListPositionAsync(ToInner(key), element, rank, maxLength, flags);
206+
207+
public Task<long[]> ListPositionsAsync(RedisKey key, RedisValue element, long count, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None) =>
208+
Inner.ListPositionsAsync(ToInner(key), element, count, rank, maxLength, flags);
209+
204210
public Task<long> ListLeftPushAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) =>
205211
Inner.ListLeftPushAsync(ToInner(key), values, flags);
206212

src/StackExchange.Redis/Message.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,9 @@ public static Message Create(int db, CommandFlags flags, RedisCommand command, i
281281
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) =>
282282
new CommandKeyValueValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3, value4);
283283

284+
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) =>
285+
new CommandKeyValueValueValueValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3, value4, value5, value6);
286+
284287
public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisValue value0, in RedisValue value1) =>
285288
new CommandValueValueMessage(db, flags, command, value0, value1);
286289

@@ -1164,6 +1167,43 @@ protected override void WriteImpl(PhysicalConnection physical)
11641167
public override int ArgCount => 6;
11651168
}
11661169

1170+
private sealed class CommandKeyValueValueValueValueValueValueValueMessage : CommandKeyBase
1171+
{
1172+
private readonly RedisValue value0, value1, value2, value3, value4, value5, value6;
1173+
1174+
public CommandKeyValueValueValueValueValueValueValueMessage(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) : base(db, flags, command, key)
1175+
{
1176+
value0.AssertNotNull();
1177+
value1.AssertNotNull();
1178+
value2.AssertNotNull();
1179+
value3.AssertNotNull();
1180+
value4.AssertNotNull();
1181+
value5.AssertNotNull();
1182+
value6.AssertNotNull();
1183+
this.value0 = value0;
1184+
this.value1 = value1;
1185+
this.value2 = value2;
1186+
this.value3 = value3;
1187+
this.value4 = value4;
1188+
this.value5 = value5;
1189+
this.value6 = value6;
1190+
}
1191+
1192+
protected override void WriteImpl(PhysicalConnection physical)
1193+
{
1194+
physical.WriteHeader(Command, ArgCount);
1195+
physical.Write(Key);
1196+
physical.WriteBulkString(value0);
1197+
physical.WriteBulkString(value1);
1198+
physical.WriteBulkString(value2);
1199+
physical.WriteBulkString(value3);
1200+
physical.WriteBulkString(value4);
1201+
physical.WriteBulkString(value5);
1202+
physical.WriteBulkString(value6);
1203+
}
1204+
public override int ArgCount => 8;
1205+
}
1206+
11671207
private sealed class CommandKeyKeyValueValueMessage : CommandKeyBase
11681208
{
11691209
private readonly RedisValue value0, value1;

src/StackExchange.Redis/PublicAPI.Shipped.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,8 @@ StackExchange.Redis.IDatabase.ListLeftPush(StackExchange.Redis.RedisKey key, Sta
547547
StackExchange.Redis.IDatabase.ListLeftPush(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.CommandFlags flags) -> long
548548
StackExchange.Redis.IDatabase.ListLeftPush(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
549549
StackExchange.Redis.IDatabase.ListLength(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
550+
StackExchange.Redis.IDatabase.ListPosition(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue element, long rank = 1, long maxLength = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
551+
StackExchange.Redis.IDatabase.ListPositions(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue element, long count, long rank = 1, long maxLength = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long[]!
550552
StackExchange.Redis.IDatabase.ListRange(StackExchange.Redis.RedisKey key, long start = 0, long stop = -1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]!
551553
StackExchange.Redis.IDatabase.ListRemove(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, long count = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
552554
StackExchange.Redis.IDatabase.ListRightPop(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.RedisValue[]!
@@ -742,6 +744,8 @@ StackExchange.Redis.IDatabaseAsync.ListLeftPushAsync(StackExchange.Redis.RedisKe
742744
StackExchange.Redis.IDatabaseAsync.ListLeftPushAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.CommandFlags flags) -> System.Threading.Tasks.Task<long>!
743745
StackExchange.Redis.IDatabaseAsync.ListLeftPushAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue[]! values, StackExchange.Redis.When when = StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<long>!
744746
StackExchange.Redis.IDatabaseAsync.ListLengthAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<long>!
747+
StackExchange.Redis.IDatabaseAsync.ListPositionAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue element, long rank = 1, long maxLength = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<long>!
748+
StackExchange.Redis.IDatabaseAsync.ListPositionsAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue element, long count, long rank = 1, long maxLength = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<long[]!>!
745749
StackExchange.Redis.IDatabaseAsync.ListRangeAsync(StackExchange.Redis.RedisKey key, long start = 0, long stop = -1, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<StackExchange.Redis.RedisValue[]!>!
746750
StackExchange.Redis.IDatabaseAsync.ListRemoveAsync(StackExchange.Redis.RedisKey key, StackExchange.Redis.RedisValue value, long count = 0, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<long>!
747751
StackExchange.Redis.IDatabaseAsync.ListRightPopAsync(StackExchange.Redis.RedisKey key, long count, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<StackExchange.Redis.RedisValue[]!>!

src/StackExchange.Redis/RedisBase.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,13 @@ public virtual Task<TimeSpan> PingAsync(CommandFlags flags = CommandFlags.None)
4040

4141
public void WaitAll(params Task[] tasks) => multiplexer.WaitAll(tasks);
4242

43-
internal virtual Task<T> ExecuteAsync<T>(Message? message, ResultProcessor<T>? processor, T defaultValue, ServerEndPoint? server = null) where T : class
43+
internal virtual Task<T> ExecuteAsync<T>(Message? message, ResultProcessor<T>? processor, T defaultValue, ServerEndPoint? server = null)
4444
{
4545
if (message is null) return CompletedTask<T>.FromDefault(defaultValue, asyncState);
4646
multiplexer.CheckMessage(message);
4747
return multiplexer.ExecuteAsyncImpl<T>(message, processor, asyncState, server, defaultValue);
4848
}
4949

50-
[return: NotNullIfNotNull("defualtValue")]
5150
internal virtual Task<T?> ExecuteAsync<T>(Message? message, ResultProcessor<T>? processor, ServerEndPoint? server = null)
5251
{
5352
if (message is null) return CompletedTask<T>.Default(asyncState);

src/StackExchange.Redis/RedisDatabase.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,18 @@ public RedisValue[] ListLeftPop(RedisKey key, long count, CommandFlags flags = C
939939
return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty<RedisValue>());
940940
}
941941

942+
public long ListPosition(RedisKey key, RedisValue element, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None)
943+
{
944+
var msg = CreateListPositionMessage(Database, flags, key, element, rank, maxLength);
945+
return ExecuteSync(msg, ResultProcessor.Int64DefaultNegativeOne, defaultValue: -1);
946+
}
947+
948+
public long[] ListPositions(RedisKey key, RedisValue element, long count, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None)
949+
{
950+
var msg = CreateListPositionMessage(Database, flags, key, element, rank, maxLength, count);
951+
return ExecuteSync(msg, ResultProcessor.Int64Array, defaultValue: Array.Empty<long>());
952+
}
953+
942954
public Task<RedisValue> ListLeftPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
943955
{
944956
var msg = Message.Create(Database, flags, RedisCommand.LPOP, key);
@@ -951,6 +963,18 @@ public Task<RedisValue[]> ListLeftPopAsync(RedisKey key, long count, CommandFlag
951963
return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty<RedisValue>());
952964
}
953965

966+
public Task<long> ListPositionAsync(RedisKey key, RedisValue element, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None)
967+
{
968+
var msg = CreateListPositionMessage(Database, flags, key, element, rank, maxLength);
969+
return ExecuteAsync(msg, ResultProcessor.Int64DefaultNegativeOne, defaultValue: -1);
970+
}
971+
972+
public Task<long[]> ListPositionsAsync(RedisKey key, RedisValue element, long count, long rank = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None)
973+
{
974+
var msg = CreateListPositionMessage(Database, flags, key, element, rank, maxLength, count);
975+
return ExecuteAsync(msg, ResultProcessor.Int64Array, defaultValue: Array.Empty<long>());
976+
}
977+
954978
public long ListLeftPush(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None)
955979
{
956980
WhenAlwaysOrExists(when);
@@ -4158,6 +4182,11 @@ protected override RedisValue[] Parse(in RawResult result, out int count)
41584182
}
41594183
}
41604184

4185+
private static Message CreateListPositionMessage(int db, CommandFlags flags, RedisKey key, RedisValue element, long rank, long maxLen, long? count = null) =>
4186+
count != null
4187+
? Message.Create(db, flags, RedisCommand.LPOS, key, element, RedisLiterals.RANK, rank, RedisLiterals.MAXLEN, maxLen, RedisLiterals.COUNT, count)
4188+
: Message.Create(db, flags, RedisCommand.LPOS, key, element, RedisLiterals.RANK, rank, RedisLiterals.MAXLEN, maxLen);
4189+
41614190
private static Message CreateSortedSetRangeStoreMessage(
41624191
int db,
41634192
CommandFlags flags,

src/StackExchange.Redis/RedisFeatures.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public readonly struct RedisFeatures
3737
v4_9_1 = new Version(4, 9, 1), // 5.0 RC1 is version 4.9.1; // 5.0 RC1 is version 4.9.1
3838
v5_0_0 = new Version(5, 0, 0),
3939
v6_0_0 = new Version(6, 0, 0),
40+
v6_0_6 = new Version(6, 0, 6),
4041
v6_2_0 = new Version(6, 2, 0),
4142
v7_0_0_rc1 = new Version(6, 9, 240); // 7.0 RC1 is version 6.9.240
4243

0 commit comments

Comments
 (0)