Skip to content

Commit 6bc9d55

Browse files
committed
parse search results correctly
1 parent 7ebee4f commit 6bc9d55

File tree

5 files changed

+93
-12
lines changed

5 files changed

+93
-12
lines changed

src/NRedisStack/PublicAPI/PublicAPI.Unshipped.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,8 @@ static NRedisStack.Search.Parameters.From<T>(T obj) -> System.Collections.Generi
6060
[NRS001]NRedisStack.Search.HybridSearchQuery.VectorSearchConfig.WithVectorData(NRedisStack.Search.VectorData! vectorData) -> NRedisStack.Search.HybridSearchQuery.VectorSearchConfig
6161
[NRS001]NRedisStack.Search.HybridSearchResult
6262
[NRS001]NRedisStack.Search.HybridSearchResult.ExecutionTime.get -> System.TimeSpan
63-
[NRS001]NRedisStack.Search.HybridSearchResult.Results.get -> System.Collections.Generic.IReadOnlyDictionary<string!, object!>![]!
63+
[NRS001]NRedisStack.Search.HybridSearchResult.Results.get -> NRedisStack.Search.Document![]!
6464
[NRS001]NRedisStack.Search.HybridSearchResult.TotalResults.get -> long
65-
[NRS001]NRedisStack.Search.HybridSearchResult.Warnings.get -> string![]!
6665
[NRS001]NRedisStack.Search.Scorer
6766
[NRS001]NRedisStack.Search.VectorData
6867
[NRS001]NRedisStack.Search.VectorSearchMethod

src/NRedisStack/Search/Document.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,54 @@ public static Document Load(string id, double score, byte[]? payload, RedisValue
5050
return ret;
5151
}
5252

53+
internal static Document Load(RedisResult src) // used from HybridSearch
54+
{
55+
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
56+
if (src is null || src.IsNull || src.Length < 0) return null!;
57+
58+
var fields = src.ToArray();
59+
string id = "";
60+
double score = double.NaN;
61+
var fieldCount = fields.Length / 2;
62+
for (int i = 0; i < fieldCount; i++)
63+
{
64+
var key = fields[2 * i];
65+
if (key.Resp2Type == ResultType.BulkString && !key.IsNull)
66+
{
67+
var blob = (byte[])key!;
68+
switch (blob.Length)
69+
{
70+
case 5 when "__key"u8.SequenceEqual(blob):
71+
id = fields[(2 * i) + 1].ToString();
72+
break;
73+
case 7 when "__score"u8.SequenceEqual(blob):
74+
score = (double)fields[(2 * i) + 1];
75+
break;
76+
}
77+
}
78+
}
79+
Document doc = new(id, score, null);
80+
for (int i = 0; i < fieldCount; i++)
81+
{
82+
var key = fields[2 * i];
83+
if (key.Resp2Type == ResultType.BulkString && !key.IsNull)
84+
{
85+
var blob = (byte[])key!;
86+
switch (blob.Length)
87+
{
88+
case 5 when "__key"u8.SequenceEqual(blob):
89+
case 7 when "__score"u8.SequenceEqual(blob):
90+
break; // skip, already parsed
91+
default:
92+
doc[key.ToString()] = (RedisValue)fields[(2 * i) + 1];
93+
break;
94+
}
95+
}
96+
}
97+
98+
return doc;
99+
}
100+
53101
public static Document Load(string id, double score, byte[]? payload, RedisValue[]? fields, string[]? scoreExplained)
54102
{
55103
Document ret = Load(id, score, payload, fields);

src/NRedisStack/Search/HybridSearchQuery.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public static class Fields
2727
/// The score from the query.
2828
/// </summary>
2929
public const string Score = "@__score";
30+
3031
// ReSharper restore InconsistentNaming
3132
}
3233
private bool _frozen;

src/NRedisStack/Search/HybridSearchResult.cs

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ internal static HybridSearchResult Parse(RedisResult? result)
3131
case ResultKey.ExecutionTime:
3232
obj.ExecutionTime = TimeSpan.FromSeconds((double)value);
3333
break;
34+
/* // defer Warnings until we've seen examples
3435
case ResultKey.Warnings when value.Length > 0:
3536
var warnings = new string[value.Length];
3637
for (int j = 0; j < value.Length; j++)
@@ -39,14 +40,9 @@ internal static HybridSearchResult Parse(RedisResult? result)
3940
}
4041
obj.Warnings = warnings;
4142
break;
43+
*/
4244
case ResultKey.Results when value.Length > 0:
43-
var rows = new IReadOnlyDictionary<string, object>[value.Length];
44-
for (int j = 0; j < value.Length; j++)
45-
{
46-
rows[j] = ParseRow(value[j]);
47-
}
48-
49-
obj.Results = rows;
45+
obj._rawResults = value.ToArray();
5046
break;
5147
}
5248
}
@@ -113,11 +109,37 @@ private enum ResultKey
113109
Results,
114110
}
115111

112+
/// <summary>
113+
/// The number of records matched.
114+
/// </summary>
116115
public long TotalResults { get; private set; } = -1; // initialize to -1 to indicate not set
117116

117+
/// <summary>
118+
/// The time taken to execute this query.
119+
/// </summary>
118120
public TimeSpan ExecutionTime { get; private set; }
119121

120-
public string[] Warnings { get; private set; } = [];
122+
// not exposing this until I've seen it being used
123+
internal string[] Warnings { get; private set; } = [];
124+
125+
private RedisResult[] _rawResults = [];
126+
private Document[]? _docResults;
127+
128+
/// <summary>
129+
/// Obtain the results as <see cref="Document"/> entries.
130+
/// </summary>
131+
public Document[] Results => _docResults ??= ParseDocResults();
121132

122-
public IReadOnlyDictionary<string, object>[] Results { get; private set; } = [];
133+
private Document[] ParseDocResults()
134+
{
135+
var raw = _rawResults;
136+
if (raw.Length == 0) return [];
137+
Document[] docs = new Document[raw.Length];
138+
for (int i = 0 ; i < raw.Length ; i ++)
139+
{
140+
docs[i] = Document.Load(raw[i]);
141+
}
142+
143+
return docs;
144+
}
123145
}

tests/NRedisStack.Tests/Search/HybridSearchIntegrationTests.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Buffers;
2+
using System.Data;
23
using System.Numerics;
34
using System.Runtime.CompilerServices;
45
using System.Runtime.InteropServices;
@@ -113,7 +114,17 @@ public async Task TestSearch(string endpointId)
113114
Assert.Equal(10, result.TotalResults);
114115
Assert.NotEqual(TimeSpan.Zero, result.ExecutionTime);
115116
Assert.Empty(result.Warnings);
117+
Assert.Same(result.Results, result.Results); // check this is not allocating each time
116118
Assert.Equal(10, result.Results.Length);
119+
foreach (var row in result.Results)
120+
{
121+
Assert.NotNull(row.Id);
122+
Assert.NotEqual("", row.Id);
123+
Assert.False(double.IsNaN(row.Score));
124+
var text1 = (string)row["text1"]!;
125+
Assert.False(string.IsNullOrWhiteSpace(text1));
126+
Log($"{row.Id}, {row.Score}, {text1}");
127+
}
117128
}
118129

119130
private void WriteArgs(string indexName, HybridSearchQuery query, IReadOnlyDictionary<string, object>? parameters = null)
@@ -146,7 +157,7 @@ private void WriteArgs(string indexName, HybridSearchQuery query, IReadOnlyDicti
146157
sb.Append(arg);
147158
}
148159
}
149-
log.WriteLine(sb.ToString());
160+
Log(sb.ToString());
150161

151162
ArrayPool<byte>.Shared.Return(scratch);
152163

0 commit comments

Comments
 (0)