Skip to content

Commit f08d0ed

Browse files
committed
- backport LCS and RedisType.VectorSet changes from v3
1 parent 8550511 commit f08d0ed

5 files changed

Lines changed: 168 additions & 20 deletions

File tree

src/StackExchange.Redis/APITypes/LCSMatchResult.cs

Lines changed: 113 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
using System;
2+
using System.ComponentModel;
23

4+
// ReSharper disable once CheckNamespace
35
namespace StackExchange.Redis;
46

57
/// <summary>
68
/// The result of a LongestCommonSubsequence command with IDX feature.
79
/// Returns a list of the positions of each sub-match.
810
/// </summary>
11+
// ReSharper disable once InconsistentNaming
912
public readonly struct LCSMatchResult
1013
{
1114
internal static LCSMatchResult Null { get; } = new LCSMatchResult(Array.Empty<LCSMatch>(), 0);
@@ -36,20 +39,92 @@ internal LCSMatchResult(LCSMatch[] matches, long matchLength)
3639
LongestMatchLength = matchLength;
3740
}
3841

42+
/// <summary>
43+
/// Represents a position range in a string.
44+
/// </summary>
45+
// ReSharper disable once InconsistentNaming
46+
public readonly struct LCSPosition : IEquatable<LCSPosition>
47+
{
48+
/// <summary>
49+
/// The start index of the position.
50+
/// </summary>
51+
public long Start { get; }
52+
53+
/// <summary>
54+
/// The end index of the position.
55+
/// </summary>
56+
public long End { get; }
57+
58+
/// <summary>
59+
/// Returns a new Position.
60+
/// </summary>
61+
/// <param name="start">The start index.</param>
62+
/// <param name="end">The end index.</param>
63+
public LCSPosition(long start, long end)
64+
{
65+
Start = start;
66+
End = end;
67+
}
68+
69+
/// <inheritdoc/>
70+
public override string ToString() => $"[{Start}..{End}]";
71+
72+
/// <inheritdoc/>
73+
public override int GetHashCode()
74+
{
75+
unchecked
76+
{
77+
return ((int)Start * 31) + (int)End;
78+
}
79+
}
80+
81+
/// <inheritdoc/>
82+
public override bool Equals(object? obj) => obj is LCSPosition other && Equals(in other);
83+
84+
/// <summary>
85+
/// Compares this position to another for equality.
86+
/// </summary>
87+
[CLSCompliant(false)]
88+
public bool Equals(in LCSPosition other) => Start == other.Start && End == other.End;
89+
90+
/// <summary>
91+
/// Compares this position to another for equality.
92+
/// </summary>
93+
bool IEquatable<LCSPosition>.Equals(LCSPosition other) => Equals(in other);
94+
}
95+
3996
/// <summary>
4097
/// Represents a sub-match of the longest match. i.e first indexes the matched substring in each string.
4198
/// </summary>
42-
public readonly struct LCSMatch
99+
// ReSharper disable once InconsistentNaming
100+
public readonly struct LCSMatch : IEquatable<LCSMatch>
43101
{
102+
private readonly LCSPosition _first;
103+
private readonly LCSPosition _second;
104+
105+
/// <summary>
106+
/// The position of the matched substring in the first string.
107+
/// </summary>
108+
public LCSPosition First => _first;
109+
110+
/// <summary>
111+
/// The position of the matched substring in the second string.
112+
/// </summary>
113+
public LCSPosition Second => _second;
114+
44115
/// <summary>
45116
/// The first index of the matched substring in the first string.
46117
/// </summary>
47-
public long FirstStringIndex { get; }
118+
[EditorBrowsable(EditorBrowsableState.Never)]
119+
[Browsable(false)]
120+
public long FirstStringIndex => _first.Start;
48121

49122
/// <summary>
50123
/// The first index of the matched substring in the second string.
51124
/// </summary>
52-
public long SecondStringIndex { get; }
125+
[EditorBrowsable(EditorBrowsableState.Never)]
126+
[Browsable(false)]
127+
public long SecondStringIndex => _second.Start;
53128

54129
/// <summary>
55130
/// The length of the match.
@@ -59,14 +134,44 @@ public readonly struct LCSMatch
59134
/// <summary>
60135
/// Returns a new Match.
61136
/// </summary>
62-
/// <param name="firstStringIndex">The first index of the matched substring in the first string.</param>
63-
/// <param name="secondStringIndex">The first index of the matched substring in the second string.</param>
137+
/// <param name="first">The position of the matched substring in the first string.</param>
138+
/// <param name="second">The position of the matched substring in the second string.</param>
64139
/// <param name="length">The length of the match.</param>
65-
internal LCSMatch(long firstStringIndex, long secondStringIndex, long length)
140+
internal LCSMatch(in LCSPosition first, in LCSPosition second, long length)
66141
{
67-
FirstStringIndex = firstStringIndex;
68-
SecondStringIndex = secondStringIndex;
142+
_first = first;
143+
_second = second;
69144
Length = length;
70145
}
146+
147+
/// <inheritdoc/>
148+
public override string ToString() => $"First: {_first}, Second: {_second}, Length: {Length}";
149+
150+
/// <inheritdoc/>
151+
public override int GetHashCode()
152+
{
153+
unchecked
154+
{
155+
int hash = 17;
156+
hash = (hash * 31) + _first.GetHashCode();
157+
hash = (hash * 31) + _second.GetHashCode();
158+
hash = (hash * 31) + Length.GetHashCode();
159+
return hash;
160+
}
161+
}
162+
163+
/// <inheritdoc/>
164+
public override bool Equals(object? obj) => obj is LCSMatch other && Equals(in other);
165+
166+
/// <summary>
167+
/// Compares this match to another for equality.
168+
/// </summary>
169+
[CLSCompliant(false)]
170+
public bool Equals(in LCSMatch other) => _first.Equals(in other._first) && _second.Equals(in other._second) && Length == other.Length;
171+
172+
/// <summary>
173+
/// Compares this match to another for equality.
174+
/// </summary>
175+
bool IEquatable<LCSMatch>.Equals(LCSMatch other) => Equals(in other);
71176
}
72177
}

src/StackExchange.Redis/Enums/RedisType.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,11 @@ public enum RedisType
6565
/// The data-type was not recognised by the client library.
6666
/// </summary>
6767
Unknown,
68+
69+
/// <summary>
70+
/// Vector sets are a data type similar to sorted sets, but instead of a score,
71+
/// vector set elements have a string representation of a vector.
72+
/// </summary>
73+
VectorSet,
6874
}
6975
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,17 @@
11
#nullable enable
2+
override StackExchange.Redis.LCSMatchResult.LCSMatch.Equals(object? obj) -> bool
3+
override StackExchange.Redis.LCSMatchResult.LCSMatch.GetHashCode() -> int
4+
override StackExchange.Redis.LCSMatchResult.LCSMatch.ToString() -> string!
5+
override StackExchange.Redis.LCSMatchResult.LCSPosition.Equals(object? obj) -> bool
6+
override StackExchange.Redis.LCSMatchResult.LCSPosition.GetHashCode() -> int
7+
override StackExchange.Redis.LCSMatchResult.LCSPosition.ToString() -> string!
8+
StackExchange.Redis.LCSMatchResult.LCSMatch.Equals(in StackExchange.Redis.LCSMatchResult.LCSMatch other) -> bool
9+
StackExchange.Redis.LCSMatchResult.LCSMatch.First.get -> StackExchange.Redis.LCSMatchResult.LCSPosition
10+
StackExchange.Redis.LCSMatchResult.LCSMatch.Second.get -> StackExchange.Redis.LCSMatchResult.LCSPosition
11+
StackExchange.Redis.LCSMatchResult.LCSPosition
12+
StackExchange.Redis.LCSMatchResult.LCSPosition.End.get -> long
13+
StackExchange.Redis.LCSMatchResult.LCSPosition.Equals(in StackExchange.Redis.LCSMatchResult.LCSPosition other) -> bool
14+
StackExchange.Redis.LCSMatchResult.LCSPosition.LCSPosition() -> void
15+
StackExchange.Redis.LCSMatchResult.LCSPosition.LCSPosition(long start, long end) -> void
16+
StackExchange.Redis.LCSMatchResult.LCSPosition.Start.get -> long
17+
StackExchange.Redis.RedisType.VectorSet = 8 -> StackExchange.Redis.RedisType

src/StackExchange.Redis/ResultProcessor.cs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1898,14 +1898,14 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
18981898
{
18991899
switch (result.Resp2TypeArray)
19001900
{
1901-
case ResultType.Array:
1902-
SetResult(message, Parse(result));
1901+
case ResultType.Array when TryParse(result, out var value):
1902+
SetResult(message, value);
19031903
return true;
19041904
}
19051905
return false;
19061906
}
19071907

1908-
private static LCSMatchResult Parse(in RawResult result)
1908+
private static bool TryParse(in RawResult result, out LCSMatchResult value)
19091909
{
19101910
var topItems = result.GetItems();
19111911
var matches = new LCSMatchResult.LCSMatch[topItems[1].GetItems().Length];
@@ -1915,14 +1915,35 @@ private static LCSMatchResult Parse(in RawResult result)
19151915
{
19161916
var matchItems = match.GetItems();
19171917

1918-
matches[i++] = new LCSMatchResult.LCSMatch(
1919-
firstStringIndex: (long)matchItems[0].GetItems()[0].AsRedisValue(),
1920-
secondStringIndex: (long)matchItems[1].GetItems()[0].AsRedisValue(),
1921-
length: (long)matchItems[2].AsRedisValue());
1918+
if (TryReadPosition(matchItems[0], out var first)
1919+
&& TryReadPosition(matchItems[1], out var second)
1920+
&& matchItems[2].TryGetInt64(out var length))
1921+
{
1922+
matches[i++] = new LCSMatchResult.LCSMatch(first, second, length);
1923+
}
1924+
else
1925+
{
1926+
value = default;
1927+
return false;
1928+
}
19221929
}
19231930
var len = (long)topItems[3].AsRedisValue();
19241931

1925-
return new LCSMatchResult(matches, len);
1932+
value = new LCSMatchResult(matches, len);
1933+
return true;
1934+
}
1935+
1936+
private static bool TryReadPosition(in RawResult raw, out LCSMatchResult.LCSPosition position)
1937+
{
1938+
// Expecting a 2-element array: [start, end]
1939+
if (raw.Resp2TypeArray is ResultType.Array && raw.ItemsCount >= 2
1940+
&& raw[0].TryGetInt64(out var start) && raw[1].TryGetInt64(out var end))
1941+
{
1942+
position = new LCSMatchResult.LCSPosition(start, end);
1943+
return true;
1944+
}
1945+
position = default;
1946+
return false;
19261947
}
19271948
}
19281949

tests/StackExchange.Redis.Tests/StringTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -967,8 +967,8 @@ public async Task LongestCommonSubsequence()
967967

968968
var stringMatchResult = db.StringLongestCommonSubsequenceWithMatches(key1, key2);
969969
Assert.Equal(2, stringMatchResult.Matches.Length); // "my" and "text" are the two matches of the result
970-
Assert.Equivalent(new LCSMatchResult.LCSMatch(4, 5, length: 4), stringMatchResult.Matches[0]); // the string "text" starts at index 4 in the first string and at index 5 in the second string
971-
Assert.Equivalent(new LCSMatchResult.LCSMatch(2, 0, length: 2), stringMatchResult.Matches[1]); // the string "my" starts at index 2 in the first string and at index 0 in the second string
970+
Assert.Equivalent(new LCSMatchResult.LCSMatch(new(4, 7), new(5, 8), length: 4), stringMatchResult.Matches[0]); // the string "text" starts at index 4 in the first string and at index 5 in the second string
971+
Assert.Equivalent(new LCSMatchResult.LCSMatch(new(2, 3), new(0, 1), length: 2), stringMatchResult.Matches[1]); // the string "my" starts at index 2 in the first string and at index 0 in the second string
972972

973973
stringMatchResult = db.StringLongestCommonSubsequenceWithMatches(key1, key2, 5);
974974
Assert.Empty(stringMatchResult.Matches); // no matches longer than 5 characters
@@ -1007,8 +1007,8 @@ public async Task LongestCommonSubsequenceAsync()
10071007

10081008
var stringMatchResult = await db.StringLongestCommonSubsequenceWithMatchesAsync(key1, key2);
10091009
Assert.Equal(2, stringMatchResult.Matches.Length); // "my" and "text" are the two matches of the result
1010-
Assert.Equivalent(new LCSMatchResult.LCSMatch(4, 5, length: 4), stringMatchResult.Matches[0]); // the string "text" starts at index 4 in the first string and at index 5 in the second string
1011-
Assert.Equivalent(new LCSMatchResult.LCSMatch(2, 0, length: 2), stringMatchResult.Matches[1]); // the string "my" starts at index 2 in the first string and at index 0 in the second string
1010+
Assert.Equivalent(new LCSMatchResult.LCSMatch(new(4, 7), new(5, 8), length: 4), stringMatchResult.Matches[0]); // the string "text" starts at index 4 in the first string and at index 5 in the second string
1011+
Assert.Equivalent(new LCSMatchResult.LCSMatch(new(2, 3), new(0, 1), length: 2), stringMatchResult.Matches[1]); // the string "my" starts at index 2 in the first string and at index 0 in the second string
10121012

10131013
stringMatchResult = await db.StringLongestCommonSubsequenceWithMatchesAsync(key1, key2, 5);
10141014
Assert.Empty(stringMatchResult.Matches); // no matches longer than 5 characters

0 commit comments

Comments
 (0)