Skip to content

Commit b213cef

Browse files
authored
CSHARP-4939: Add VectorSearchScore builder for $vectorSearch stage (#1252)
1 parent fcfaa7d commit b213cef

File tree

4 files changed

+236
-21
lines changed

4 files changed

+236
-21
lines changed

src/MongoDB.Driver/ProjectionDefinitionBuilder.cs

Lines changed: 185 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,24 @@ public static ProjectionDefinition<TDocument> Meta<TDocument>(this ProjectionDef
167167
/// </returns>
168168
public static ProjectionDefinition<TDocument> MetaSearchHighlights<TDocument>(
169169
this ProjectionDefinition<TDocument> projection,
170-
string field)
170+
FieldDefinition<TDocument> field)
171+
{
172+
var builder = Builders<TDocument>.Projection;
173+
return builder.Combine(projection, builder.MetaSearchHighlights(field));
174+
}
175+
176+
/// <summary>
177+
/// Combines an existing projection with a search highlights projection.
178+
/// </summary>
179+
/// <typeparam name="TDocument">The type of the document.</typeparam>
180+
/// <param name="projection">The projection.</param>
181+
/// <param name="field">The field.</param>
182+
/// <returns>
183+
/// A combined projection.
184+
/// </returns>
185+
public static ProjectionDefinition<TDocument> MetaSearchHighlights<TDocument>(
186+
this ProjectionDefinition<TDocument> projection,
187+
Expression<Func<TDocument, object>> field)
171188
{
172189
var builder = Builders<TDocument>.Projection;
173190
return builder.Combine(projection, builder.MetaSearchHighlights(field));
@@ -184,12 +201,46 @@ public static ProjectionDefinition<TDocument> MetaSearchHighlights<TDocument>(
184201
/// </returns>
185202
public static ProjectionDefinition<TDocument> MetaSearchScore<TDocument>(
186203
this ProjectionDefinition<TDocument> projection,
187-
string field)
204+
FieldDefinition<TDocument> field)
188205
{
189206
var builder = Builders<TDocument>.Projection;
190207
return builder.Combine(projection, builder.MetaSearchScore(field));
191208
}
192209

210+
/// <summary>
211+
/// Combines an existing projection with a search score projection.
212+
/// </summary>
213+
/// <typeparam name="TDocument">The type of the document.</typeparam>
214+
/// <param name="projection">The projection.</param>
215+
/// <param name="field">The field.</param>
216+
/// <returns>
217+
/// A combined projection.
218+
/// </returns>
219+
public static ProjectionDefinition<TDocument> MetaSearchScore<TDocument>(
220+
this ProjectionDefinition<TDocument> projection,
221+
Expression<Func<TDocument, object>> field)
222+
{
223+
var builder = Builders<TDocument>.Projection;
224+
return builder.Combine(projection, builder.MetaSearchScore(field));
225+
}
226+
227+
/// <summary>
228+
/// Combines an existing projection with a search score details projection.
229+
/// </summary>
230+
/// <typeparam name="TDocument">The type of the document.</typeparam>
231+
/// <param name="projection">The projection.</param>
232+
/// <param name="field">The field.</param>
233+
/// <returns>
234+
/// A combined projection.
235+
/// </returns>
236+
public static ProjectionDefinition<TDocument> MetaSearchScoreDetails<TDocument>(
237+
this ProjectionDefinition<TDocument> projection,
238+
FieldDefinition<TDocument> field)
239+
{
240+
var builder = Builders<TDocument>.Projection;
241+
return builder.Combine(projection, builder.MetaSearchScoreDetails(field));
242+
}
243+
193244
/// <summary>
194245
/// Combines an existing projection with a search score details projection.
195246
/// </summary>
@@ -201,7 +252,7 @@ public static ProjectionDefinition<TDocument> MetaSearchScore<TDocument>(
201252
/// </returns>
202253
public static ProjectionDefinition<TDocument> MetaSearchScoreDetails<TDocument>(
203254
this ProjectionDefinition<TDocument> projection,
204-
string field)
255+
Expression<Func<TDocument, object>> field)
205256
{
206257
var builder = Builders<TDocument>.Projection;
207258
return builder.Combine(projection, builder.MetaSearchScoreDetails(field));
@@ -216,12 +267,65 @@ public static ProjectionDefinition<TDocument> MetaSearchScoreDetails<TDocument>(
216267
/// <returns>
217268
/// A combined projection.
218269
/// </returns>
219-
public static ProjectionDefinition<TDocument> MetaTextScore<TDocument>(this ProjectionDefinition<TDocument> projection, string field)
270+
public static ProjectionDefinition<TDocument> MetaTextScore<TDocument>(
271+
this ProjectionDefinition<TDocument> projection,
272+
FieldDefinition<TDocument> field)
220273
{
221274
var builder = Builders<TDocument>.Projection;
222275
return builder.Combine(projection, builder.MetaTextScore(field));
223276
}
224277

278+
/// <summary>
279+
/// Combines an existing projection with a text score projection.
280+
/// </summary>
281+
/// <typeparam name="TDocument">The type of the document.</typeparam>
282+
/// <param name="projection">The projection.</param>
283+
/// <param name="field">The field.</param>
284+
/// <returns>
285+
/// A combined projection.
286+
/// </returns>
287+
public static ProjectionDefinition<TDocument> MetaTextScore<TDocument>(
288+
this ProjectionDefinition<TDocument> projection,
289+
Expression<Func<TDocument, object>> field)
290+
{
291+
var builder = Builders<TDocument>.Projection;
292+
return builder.Combine(projection, builder.MetaTextScore(field));
293+
}
294+
295+
/// <summary>
296+
/// Combines an existing projection with a VectorSearch score projection.
297+
/// </summary>
298+
/// <typeparam name="TDocument">The type of the document.</typeparam>
299+
/// <param name="projection">The projection.</param>
300+
/// <param name="field">The field.</param>
301+
/// <returns>
302+
/// A combined projection.
303+
/// </returns>
304+
public static ProjectionDefinition<TDocument> MetaVectorSearchScore<TDocument>(
305+
this ProjectionDefinition<TDocument> projection,
306+
FieldDefinition<TDocument> field)
307+
{
308+
var builder = Builders<TDocument>.Projection;
309+
return builder.Combine(projection, builder.MetaVectorSearchScore(field));
310+
}
311+
312+
/// <summary>
313+
/// Combines an existing projection with a VectorSearch score projection.
314+
/// </summary>
315+
/// <typeparam name="TDocument">The type of the document.</typeparam>
316+
/// <param name="projection">The projection.</param>
317+
/// <param name="field">The field.</param>
318+
/// <returns>
319+
/// A combined projection.
320+
/// </returns>
321+
public static ProjectionDefinition<TDocument> MetaVectorSearchScore<TDocument>(
322+
this ProjectionDefinition<TDocument> projection,
323+
Expression<Func<TDocument, object>> field)
324+
{
325+
var builder = Builders<TDocument>.Projection;
326+
return builder.Combine(projection, builder.MetaVectorSearchScore(field));
327+
}
328+
225329
/// <summary>
226330
/// Combines an existing projection with a search metadata projection.
227331
/// </summary>
@@ -475,7 +579,7 @@ public ProjectionDefinition<TSource> Include(Expression<Func<TSource, object>> f
475579
/// <returns>
476580
/// A text score projection.
477581
/// </returns>
478-
public ProjectionDefinition<TSource> Meta(string field, string metaFieldName)
582+
public ProjectionDefinition<TSource> Meta(FieldDefinition<TSource> field, string metaFieldName)
479583
{
480584
return new SingleFieldProjectionDefinition<TSource>(field, new BsonDocument("$meta", metaFieldName));
481585
}
@@ -487,47 +591,119 @@ public ProjectionDefinition<TSource> Meta(string field, string metaFieldName)
487591
/// <returns>
488592
/// A search highlights projection.
489593
/// </returns>
490-
public ProjectionDefinition<TSource> MetaSearchHighlights(string field)
594+
public ProjectionDefinition<TSource> MetaSearchHighlights(FieldDefinition<TSource> field)
491595
{
492596
return Meta(field, "searchHighlights");
493597
}
494598

599+
/// <summary>
600+
/// Creates a search highlights projection.
601+
/// </summary>
602+
/// <param name="field">The field.</param>
603+
/// <returns>
604+
/// A search highlights projection.
605+
/// </returns>
606+
public ProjectionDefinition<TSource> MetaSearchHighlights<TField>(Expression<Func<TSource, TField>> field)
607+
{
608+
return MetaSearchHighlights(new ExpressionFieldDefinition<TSource>(field));
609+
}
610+
495611
/// <summary>
496612
/// Creates a search score projection.
497613
/// </summary>
498614
/// <param name="field">The field.</param>
499615
/// <returns>
500616
/// A search score projection.
501617
/// </returns>
502-
public ProjectionDefinition<TSource> MetaSearchScore(string field)
618+
public ProjectionDefinition<TSource> MetaSearchScore(FieldDefinition<TSource> field)
503619
{
504620
return Meta(field, "searchScore");
505621
}
506622

623+
/// <summary>
624+
/// Creates a search score projection.
625+
/// </summary>
626+
/// <param name="field">The field.</param>
627+
/// <returns>
628+
/// A search score projection.
629+
/// </returns>
630+
public ProjectionDefinition<TSource> MetaSearchScore<TField>(Expression<Func<TSource, TField>> field)
631+
{
632+
return MetaSearchScore(new ExpressionFieldDefinition<TSource>(field));
633+
}
634+
507635
/// <summary>
508636
/// Creates a search score details projection.
509637
/// </summary>
510638
/// <param name="field">The field.</param>
511639
/// <returns>
512640
/// A search score details projection.
513641
/// </returns>
514-
public ProjectionDefinition<TSource> MetaSearchScoreDetails(string field)
642+
public ProjectionDefinition<TSource> MetaSearchScoreDetails(FieldDefinition<TSource> field)
515643
{
516644
return Meta(field, "searchScoreDetails");
517645
}
518646

647+
/// <summary>
648+
/// Creates a search score details projection.
649+
/// </summary>
650+
/// <param name="field">The field.</param>
651+
/// <returns>
652+
/// A search score details projection.
653+
/// </returns>
654+
public ProjectionDefinition<TSource> MetaSearchScoreDetails<TField>(Expression<Func<TSource, TField>> field)
655+
{
656+
return MetaSearchScoreDetails(new ExpressionFieldDefinition<TSource>(field));
657+
}
658+
519659
/// <summary>
520660
/// Creates a text score projection.
521661
/// </summary>
522662
/// <param name="field">The field.</param>
523663
/// <returns>
524664
/// A text score projection.
525665
/// </returns>
526-
public ProjectionDefinition<TSource> MetaTextScore(string field)
666+
public ProjectionDefinition<TSource> MetaTextScore(FieldDefinition<TSource> field)
527667
{
528668
return Meta(field, "textScore");
529669
}
530670

671+
/// <summary>
672+
/// Creates a text score projection.
673+
/// </summary>
674+
/// <param name="field">The field.</param>
675+
/// <returns>
676+
/// A text score projection.
677+
/// </returns>
678+
public ProjectionDefinition<TSource> MetaTextScore<TField>(Expression<Func<TSource, TField>> field)
679+
{
680+
return MetaTextScore(new ExpressionFieldDefinition<TSource>(field));
681+
}
682+
683+
/// <summary>
684+
/// Creates a VectorSearch score projection.
685+
/// </summary>
686+
/// <param name="field">The field.</param>
687+
/// <returns>
688+
/// A VectorSearch score projection.
689+
/// </returns>
690+
public ProjectionDefinition<TSource> MetaVectorSearchScore(FieldDefinition<TSource> field)
691+
{
692+
return Meta(field, "vectorSearchScore");
693+
}
694+
695+
/// <summary>
696+
/// Creates a VectorSearch score projection.
697+
/// </summary>
698+
/// <param name="field">The field.</param>
699+
/// <returns>
700+
/// A VectorSearch score projection.
701+
/// </returns>
702+
public ProjectionDefinition<TSource> MetaVectorSearchScore<TField>(Expression<Func<TSource, TField>> field)
703+
{
704+
return MetaVectorSearchScore(new ExpressionFieldDefinition<TSource>(field));
705+
}
706+
531707
/// <summary>
532708
/// Creates a search metadata projection.
533709
/// </summary>

tests/MongoDB.Driver.Tests/ProjectionDefinitionBuilderTests.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,14 @@ public void MetaTextScore()
162162
Assert(subject.MetaTextScore("a"), "{a: {$meta: 'textScore'}}");
163163
}
164164

165+
[Fact]
166+
public void MetaTextScore_typed()
167+
{
168+
var subject = CreateSubject<Person>();
169+
170+
Assert(subject.MetaTextScore(p => p.FirstName), "{fn: {$meta: 'textScore'}}");
171+
}
172+
165173
[Fact]
166174
public void Slice()
167175
{

tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public void Compound()
8989
var projectionDefinition = Builders.Projection
9090
.Include(x => x.Body)
9191
.Include(x => x.Title)
92-
.MetaSearchScore("score");
92+
.MetaSearchScore(x => x.Score);
9393

9494
var result = SearchSingle(searchDefinition, projectionDefinition);
9595
result.Title.Should().Be("Declaration of Independence");
@@ -299,9 +299,9 @@ public void Phrase()
299299
.Project<HistoricalDocument>(Builders.Projection
300300
.Include(x => x.Title)
301301
.Include(x => x.Body)
302-
.MetaSearchScore("score")
303-
.MetaSearchHighlights("highlights")
304-
.MetaSearchScoreDetails("scoreDetails"))
302+
.MetaSearchScore(x => x.Score)
303+
.MetaSearchHighlights(x => x.Highlights)
304+
.MetaSearchScoreDetails(x => x.ScoreDetails))
305305
.ToList();
306306

307307
var result = results.Should().ContainSingle().Subject;
@@ -470,7 +470,7 @@ public void Sort_MetaSearchScore()
470470
new() { Sort = Builders<Movie>.Sort.MetaSearchScoreAscending() })
471471
.Project<Movie>(Builders<Movie>.Projection
472472
.Include(x => x.Title)
473-
.MetaSearchScore("score"))
473+
.MetaSearchScore(x => x.Score))
474474
.Limit(10)
475475
.ToList();
476476
results.First().Title.Should().Be("Invitation to the Dance");
@@ -572,13 +572,16 @@ public void VectorSearch()
572572
IndexName = "sample_mflix__embedded_movies"
573573
};
574574

575-
var actualTitles = GetEmbeddedMoviesCollection()
575+
var results = GetEmbeddedMoviesCollection()
576576
.Aggregate()
577577
.VectorSearch(m => m.Embedding, vector, 5, options)
578-
.Project(Builders<EmbeddedMovie>.Projection.Expression(m => m.Title))
578+
.Project<EmbeddedMovie>(Builders<EmbeddedMovie>.Projection
579+
.Include(m => m.Title)
580+
.MetaVectorSearchScore(p => p.Score))
579581
.ToList();
580582

581-
actualTitles.ShouldBeEquivalentTo(expectedTitles);
583+
results.Select(m => m.Title).ShouldBeEquivalentTo(expectedTitles);
584+
results.Should().OnlyContain(m => m.Score > 0.9);
582585
}
583586

584587
[Fact]
@@ -737,6 +740,9 @@ public class EmbeddedMovie
737740

738741
[BsonElement("plot_embedding")]
739742
public double[] Embedding { get; set; }
743+
744+
[BsonElement("score")]
745+
public double Score { get; set; }
740746
}
741747
}
742748
}

0 commit comments

Comments
 (0)