Skip to content

Commit c992d83

Browse files
committed
push all the changes
1 parent 7c7d7fd commit c992d83

File tree

5 files changed

+67
-29
lines changed

5 files changed

+67
-29
lines changed

src/NRedisStack/Search/HybridSearchQuery.Command.cs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,25 @@
1-
using System.Buffers;
21
using System.Diagnostics;
3-
using System.Runtime.InteropServices;
42
using NRedisStack.Search.Aggregation;
5-
using StackExchange.Redis;
63

74
namespace NRedisStack.Search;
85

96
public sealed partial class HybridSearchQuery
107
{
118
internal string Command => "FT.HYBRID";
129

13-
internal ICollection<object> GetArgs(string index)
10+
internal ICollection<object> GetArgs(string index, IReadOnlyDictionary<string, object>? parameters)
1411
{
15-
var count = GetOwnArgsCount();
12+
_frozen = true;
13+
var count = GetOwnArgsCount(parameters);
1614
var args = new List<object>(count + 1);
1715
args.Add(index);
18-
AddOwnArgs(args);
16+
AddOwnArgs(args, parameters);
1917
Debug.Assert(args.Count == count + 1,
2018
$"Arg count mismatch; check {nameof(GetOwnArgsCount)} ({count}) vs {nameof(AddOwnArgs)} ({args.Count - 1})");
2119
return args;
2220
}
2321

24-
internal int GetOwnArgsCount()
22+
internal int GetOwnArgsCount(IReadOnlyDictionary<string, object>? parameters)
2523
{
2624
int count = _search.GetOwnArgsCount() + _vsim.GetOwnArgsCount(); // note index is not included here
2725

@@ -119,15 +117,18 @@ internal int GetOwnArgsCount()
119117

120118
count += fields.Length;
121119
break;
122-
}
120+
}
123121
}
124122
}
125123

126124
if (_filter is not null) count += 2;
127125

128126
if (_pagingOffset >= 0) count += 3;
129127

130-
if (_parameters is not null) count += (_parameters.Count + 1) * 2;
128+
if (parameters is not null)
129+
{
130+
count += (parameters.Count + 1) * 2;
131+
}
131132

132133
if (_explainScore) count++;
133134
if (_timeout) count++;
@@ -142,7 +143,7 @@ internal int GetOwnArgsCount()
142143
return count;
143144
}
144145

145-
internal void AddOwnArgs(List<object> args)
146+
internal void AddOwnArgs(List<object> args, IReadOnlyDictionary<string, object>? parameters)
146147
{
147148
_search.AddOwnArgs(args);
148149
_vsim.AddOwnArgs(args);
@@ -291,7 +292,7 @@ static void AddApply(in ApplyExpression expr, List<object> args)
291292
break;
292293
default:
293294
throw new ArgumentException("Invalid sort by field or fields");
294-
}
295+
}
295296
}
296297
}
297298

@@ -308,24 +309,24 @@ static void AddApply(in ApplyExpression expr, List<object> args)
308309
args.Add(_pagingCount);
309310
}
310311

311-
if (_parameters is not null)
312+
if (parameters is not null)
312313
{
313314
args.Add("PARAMS");
314-
args.Add(_parameters.Count * 2);
315-
if (_parameters is Dictionary<string, object> typed)
315+
args.Add(parameters.Count * 2);
316+
if (parameters is Dictionary<string, object> typed)
316317
{
317318
foreach (var entry in typed) // avoid allocating enumerator
318319
{
319320
args.Add(entry.Key);
320-
args.Add(entry.Value);
321+
args.Add(entry.Value is VectorData vec ? vec.GetSingleArg() : entry.Value);
321322
}
322323
}
323324
else
324325
{
325-
foreach (var entry in _parameters)
326+
foreach (var entry in parameters)
326327
{
327328
args.Add(entry.Key);
328-
args.Add(entry.Value);
329+
args.Add(entry.Value is VectorData vec ? vec.GetSingleArg() : entry.Value);
329330
}
330331
}
331332
}

src/NRedisStack/Search/HybridSearchQuery.SearchConfig.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ namespace NRedisStack.Search;
44

55
public sealed partial class HybridSearchQuery
66
{
7+
/// <summary>
8+
/// Represents a search query. For a parameterized query, a search like <c>"$key"</c> will search using the parameter named <c>key</c>.
9+
/// </summary>
710
public readonly struct SearchConfig(string query, Scorer? scorer = null, string? scoreAlias = null)
811
{
912
private readonly string _query = query;

src/NRedisStack/Search/HybridSearchQuery.VectorSearchConfig.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
using System.Buffers;
2-
using System.Numerics;
31
using System.Runtime.CompilerServices;
4-
using System.Runtime.InteropServices;
52

63
namespace NRedisStack.Search;
74

@@ -97,7 +94,7 @@ internal int GetOwnArgsCount()
9794
int count = 0;
9895
if (HasValue)
9996
{
100-
count += 2 + _vectorData.Base64ArgsCount();
97+
count += 3;
10198
if (_method != null) count += _method.GetOwnArgsCount();
10299
if (_filter != null) count += 2;
103100

@@ -112,7 +109,7 @@ internal void AddOwnArgs(List<object> args)
112109
{
113110
args.Add("VSIM");
114111
args.Add(_fieldName);
115-
_vectorData.AddBase64Args(args);
112+
args.Add(_vectorData.GetSingleArg());
116113

117114
_method?.AddOwnArgs(args);
118115
if (_filter != null)

src/NRedisStack/Search/HybridSearchQuery.cs

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,34 @@
44

55
namespace NRedisStack.Search;
66

7+
/// <summary>
8+
/// Represents a hybrid search (FT.HYBRID) operation. Note that <see cref="HybridSearchQuery"/> instances can be reused for
9+
/// common queries, by passing the search operands as named parameters.
10+
/// </summary>
711
[Experimental(Experiments.Server_8_4, UrlFormat = Experiments.UrlFormat)]
812
public sealed partial class HybridSearchQuery
913
{
14+
private bool _frozen;
1015
private SearchConfig _search;
1116
private VectorSearchConfig _vsim;
1217

18+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
19+
private HybridSearchQuery ThrowIfFrozen() // GetArgs freezes
20+
{
21+
if (_frozen) Throw();
22+
return this;
23+
24+
[MethodImpl(MethodImplOptions.NoInlining)]
25+
static void Throw() => throw new InvalidOperationException(
26+
"By default, the query cannot be mutated after being issued (to allow safe parameterized reuse from concurrent callers). If you are using the query sequentially rather than concurrently, you can use " + nameof(AllowModification) + " to re-enable changes.");
27+
}
1328
/// <summary>
1429
/// Specify the textual search portion of the query.
30+
/// For a parameterized query, a search like <c>"$key"</c> will search using the parameter named <c>key</c>.
1531
/// </summary>
1632
public HybridSearchQuery Search(SearchConfig query)
1733
{
34+
ThrowIfFrozen();
1835
_search = query;
1936
return this;
2037
}
@@ -30,6 +47,7 @@ public HybridSearchQuery VectorSearch(string fieldName, VectorData vectorData)
3047
/// </summary>
3148
public HybridSearchQuery VectorSearch(VectorSearchConfig config)
3249
{
50+
ThrowIfFrozen();
3351
_vsim = config;
3452
return this;
3553
}
@@ -42,6 +60,7 @@ public HybridSearchQuery VectorSearch(VectorSearchConfig config)
4260
/// </summary>
4361
public HybridSearchQuery Combine(Combiner combiner, string? scoreAlias = null)
4462
{
63+
ThrowIfFrozen();
4564
_combiner = combiner;
4665
_combineScoreAlias = scoreAlias;
4766
return this;
@@ -54,6 +73,7 @@ public HybridSearchQuery Combine(Combiner combiner, string? scoreAlias = null)
5473
/// </summary>
5574
public HybridSearchQuery ReturnFields(params string[] fields) // naming for consistency with SearchQuery
5675
{
76+
ThrowIfFrozen();
5777
_loadFieldOrFields = NullIfEmpty(fields);
5878
return this;
5979
}
@@ -63,6 +83,7 @@ public HybridSearchQuery Combine(Combiner combiner, string? scoreAlias = null)
6383
/// </summary>
6484
public HybridSearchQuery ReturnFields(string field) // naming for consistency with SearchQuery
6585
{
86+
ThrowIfFrozen();
6687
_loadFieldOrFields = field;
6788
return this;
6889
}
@@ -75,6 +96,7 @@ public HybridSearchQuery Combine(Combiner combiner, string? scoreAlias = null)
7596
/// </summary>
7697
public HybridSearchQuery GroupBy(string field)
7798
{
99+
ThrowIfFrozen();
78100
_groupByFieldOrFields = field;
79101
return this;
80102
}
@@ -84,6 +106,7 @@ public HybridSearchQuery GroupBy(string field)
84106
/// </summary>
85107
public HybridSearchQuery GroupBy(params string[] fields)
86108
{
109+
ThrowIfFrozen();
87110
_groupByFieldOrFields = NullIfEmpty(fields);
88111
return this;
89112
}
@@ -93,6 +116,7 @@ public HybridSearchQuery GroupBy(params string[] fields)
93116
/// </summary>
94117
public HybridSearchQuery Reduce(Reducer reducer)
95118
{
119+
ThrowIfFrozen();
96120
_reducerOrReducers = reducer;
97121
return this;
98122
}
@@ -102,6 +126,7 @@ public HybridSearchQuery Reduce(Reducer reducer)
102126
/// </summary>
103127
public HybridSearchQuery Reduce(params Reducer[] reducers)
104128
{
129+
ThrowIfFrozen();
105130
_reducerOrReducers = NullIfEmpty(reducers);
106131
return this;
107132
}
@@ -116,6 +141,7 @@ public HybridSearchQuery Reduce(params Reducer[] reducers)
116141
[OverloadResolutionPriority(1)] // allow Apply(new("expr", "alias")) to resolve correctly
117142
public HybridSearchQuery Apply(ApplyExpression applyExpression)
118143
{
144+
ThrowIfFrozen();
119145
if (applyExpression.Alias is null)
120146
{
121147
_applyExpressionOrExpressions = applyExpression.Expression;
@@ -133,6 +159,7 @@ public HybridSearchQuery Apply(ApplyExpression applyExpression)
133159
/// </summary>
134160
public HybridSearchQuery Apply(params ApplyExpression[] applyExpression)
135161
{
162+
ThrowIfFrozen();
136163
_applyExpressionOrExpressions = NullIfEmpty(applyExpression);
137164
return this;
138165
}
@@ -145,6 +172,7 @@ public HybridSearchQuery Apply(params ApplyExpression[] applyExpression)
145172
/// <remarks>The default sort order is by score, unless overridden or disabled.</remarks>
146173
public HybridSearchQuery SortBy(params SortedField[] fields)
147174
{
175+
ThrowIfFrozen();
148176
_sortByFieldOrFields = NullIfEmpty(fields);
149177
return this;
150178
}
@@ -154,6 +182,7 @@ public HybridSearchQuery SortBy(params SortedField[] fields)
154182
/// </summary>
155183
public HybridSearchQuery NoSort()
156184
{
185+
ThrowIfFrozen();
157186
_sortByFieldOrFields = s_NoSortSentinel;
158187
return this;
159188
}
@@ -165,6 +194,7 @@ public HybridSearchQuery NoSort()
165194
/// </summary>
166195
public HybridSearchQuery SortBy(params string[] fields)
167196
{
197+
ThrowIfFrozen();
168198
_sortByFieldOrFields = NullIfEmpty(fields);
169199
return this;
170200
}
@@ -174,6 +204,7 @@ public HybridSearchQuery SortBy(params string[] fields)
174204
/// </summary>
175205
public HybridSearchQuery SortBy(SortedField field)
176206
{
207+
ThrowIfFrozen();
177208
_sortByFieldOrFields = field;
178209
return this;
179210
}
@@ -183,6 +214,7 @@ public HybridSearchQuery SortBy(SortedField field)
183214
/// </summary>
184215
public HybridSearchQuery SortBy(string field)
185216
{
217+
ThrowIfFrozen();
186218
_sortByFieldOrFields = field;
187219
return this;
188220
}
@@ -194,6 +226,7 @@ public HybridSearchQuery SortBy(string field)
194226
/// </summary>
195227
public HybridSearchQuery Filter(string expression)
196228
{
229+
ThrowIfFrozen();
197230
_filter = expression;
198231
return this;
199232
}
@@ -202,6 +235,7 @@ public HybridSearchQuery Filter(string expression)
202235

203236
public HybridSearchQuery Limit(int offset, int count)
204237
{
238+
ThrowIfFrozen();
205239
if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset));
206240
if (count < 0) throw new ArgumentOutOfRangeException(nameof(count));
207241
_pagingOffset = offset;
@@ -216,6 +250,7 @@ public HybridSearchQuery Limit(int offset, int count)
216250
/// </summary>
217251
public HybridSearchQuery ExplainScore(bool explainScore = true)
218252
{
253+
ThrowIfFrozen();
219254
_explainScore = explainScore;
220255
return this;
221256
}
@@ -227,6 +262,7 @@ public HybridSearchQuery ExplainScore(bool explainScore = true)
227262
/// </summary>
228263
public HybridSearchQuery Timeout(bool timeout = true)
229264
{
265+
ThrowIfFrozen();
230266
_timeout = timeout;
231267
return this;
232268
}
@@ -239,20 +275,22 @@ public HybridSearchQuery Timeout(bool timeout = true)
239275
/// </summary>
240276
internal HybridSearchQuery WithCursor(int count = 0, TimeSpan maxIdle = default)
241277
{
278+
ThrowIfFrozen();
242279
// not currently exposed, while I figure out the API
243280
_cursorCount = count;
244281
_cursorMaxIdle = maxIdle;
245282
return this;
246283
}
247284

248-
private IReadOnlyDictionary<string, object>? _parameters;
249-
250285
/// <summary>
251-
/// Supply parameters for the query.
286+
/// By default, queries are frozen when issued, to allow safe re-use of prepared queries from different callers.
287+
/// If you instead want to make sequential use of a query in a <i>single</i> caller, you can use this method
288+
/// to re-enable modification after issuing each query.
252289
/// </summary>
253-
public HybridSearchQuery Parameters(IReadOnlyDictionary<string, object> parameters)
290+
/// <returns></returns>
291+
public HybridSearchQuery AllowModification()
254292
{
255-
_parameters = parameters is { Count: 0 } ? null : parameters; // ignore empty
293+
_frozen = false;
256294
return this;
257295
}
258296
}

tests/NRedisStack.Tests/Search/HybridSearchIntegrationTests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,8 @@ public void TestSetup(string endpointId)
5151
var query = new HybridSearchQuery()
5252
.Search("*")
5353
.VectorSearch("@vector1", new float[] { 1, 2, 3, 4 })
54-
.Parameters(args)
5554
.ReturnFields("@text1");
56-
var result = api.FT.HybridSearch(api.Index, query);
55+
var result = api.FT.HybridSearch(api.Index, query, args);
5756
Assert.Equal(0, result.TotalResults);
5857
Assert.NotEqual(TimeSpan.Zero, result.ExecutionTime);
5958
Assert.Empty(result.Warnings);

0 commit comments

Comments
 (0)