Skip to content

Commit 407dbac

Browse files
authored
CSHARP-4477: Implement embeddedDocuments operator in Atlas Search (#1155)
1 parent 5412f33 commit 407dbac

22 files changed

+422
-134
lines changed

src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,8 +1364,9 @@ public static PipelineStageDefinition<TInput, TInput> Search<TInput>(
13641364
operatorName,
13651365
(s, sr, linqProvider) =>
13661366
{
1367-
var renderedSearchDefinition = searchDefinition.Render(s, sr);
1368-
renderedSearchDefinition.Add("highlight", () => searchOptions.Highlight.Render(s, sr), searchOptions.Highlight != null);
1367+
var renderContext = new SearchDefinitionRenderContext<TInput>(s, sr);
1368+
var renderedSearchDefinition = searchDefinition.Render(renderContext);
1369+
renderedSearchDefinition.Add("highlight", () => searchOptions.Highlight.Render(renderContext), searchOptions.Highlight != null);
13691370
renderedSearchDefinition.Add("count", () => searchOptions.CountOptions.Render(), searchOptions.CountOptions != null);
13701371
renderedSearchDefinition.Add("sort", () => searchOptions.Sort.Render(s, sr), searchOptions.Sort != null);
13711372
renderedSearchDefinition.Add("index", searchOptions.IndexName, searchOptions.IndexName != null);
@@ -1400,7 +1401,7 @@ public static PipelineStageDefinition<TInput, SearchMetaResult> SearchMeta<TInpu
14001401
operatorName,
14011402
(s, sr, linqProvider) =>
14021403
{
1403-
var renderedSearchDefinition = searchDefinition.Render(s, sr);
1404+
var renderedSearchDefinition = searchDefinition.Render(new(s, sr));
14041405
renderedSearchDefinition.Add("count", () => count.Render(), count != null);
14051406
renderedSearchDefinition.Add("index", indexName, indexName != null);
14061407

src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public AutocompleteSearchDefinition(
4242
_fuzzy = fuzzy;
4343
}
4444

45-
private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
45+
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
4646
new()
4747
{
4848
{ "query", _query.Render() },
@@ -76,7 +76,7 @@ public CompoundSearchDefinition(
7676
_minimumShouldMatch = minimumShouldMatch;
7777
}
7878

79-
private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry)
79+
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext)
8080
{
8181
return new()
8282
{
@@ -88,7 +88,33 @@ private protected override BsonDocument RenderArguments(IBsonSerializer<TDocumen
8888
};
8989

9090
Func<BsonArray> Render(List<SearchDefinition<TDocument>> searchDefinitions) =>
91-
() => new BsonArray(searchDefinitions.Select(clause => clause.Render(documentSerializer, serializerRegistry)));
91+
() => new BsonArray(searchDefinitions.Select(clause => clause.Render(renderContext)));
92+
}
93+
}
94+
95+
internal sealed class EmbeddedDocumentSearchDefinition<TDocument, TField> : OperatorSearchDefinition<TDocument>
96+
{
97+
private readonly SearchDefinition<TField> _operator;
98+
99+
public EmbeddedDocumentSearchDefinition(FieldDefinition<TDocument, IEnumerable<TField>> path, SearchDefinition<TField> @operator, SearchScoreDefinition<TDocument> score)
100+
: base(OperatorType.EmbeddedDocument,
101+
new SingleSearchPathDefinition<TDocument>(path),
102+
score)
103+
{
104+
_operator = Ensure.IsNotNull(@operator, nameof(@operator));
105+
}
106+
107+
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext)
108+
{
109+
// Add base path to all nested operator paths
110+
var pathPrefix = _path.Render(renderContext).AsString;
111+
112+
var newRenderContext = new SearchDefinitionRenderContext<TField>(
113+
renderContext.SerializerRegistry.GetSerializer<TField>(),
114+
renderContext.SerializerRegistry,
115+
pathPrefix);
116+
117+
return new("operator", _operator.Render(newRenderContext));
92118
}
93119
}
94120

@@ -102,7 +128,7 @@ public EqualsSearchDefinition(FieldDefinition<TDocument> path, TField value, Sea
102128
_value = ToBsonValue(value);
103129
}
104130

105-
private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
131+
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
106132
new("value", _value);
107133

108134
private static BsonValue ToBsonValue(TField value) =>
@@ -145,11 +171,11 @@ public FacetSearchDefinition(SearchDefinition<TDocument> @operator, IEnumerable<
145171
_facets = Ensure.IsNotNull(facets, nameof(facets)).ToArray();
146172
}
147173

148-
private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
174+
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
149175
new()
150176
{
151-
{ "operator", _operator.Render(documentSerializer, serializerRegistry) },
152-
{ "facets", new BsonDocument(_facets.Select(f => new BsonElement(f.Name, f.Render(documentSerializer, serializerRegistry)))) }
177+
{ "operator", _operator.Render(renderContext) },
178+
{ "facets", new BsonDocument(_facets.Select(f => new BsonElement(f.Name, f.Render(renderContext)))) }
153179
};
154180
}
155181

@@ -170,7 +196,7 @@ public GeoShapeSearchDefinition(
170196
_relation = relation;
171197
}
172198

173-
private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
199+
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
174200
new()
175201
{
176202
{ "geometry", _geometry.ToBsonDocument() },
@@ -192,7 +218,7 @@ public GeoWithinSearchDefinition(
192218
_area = Ensure.IsNotNull(area, nameof(area));
193219
}
194220

195-
private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
221+
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
196222
new(_area.Render());
197223
}
198224

@@ -206,13 +232,13 @@ public MoreLikeThisSearchDefinition(IEnumerable<TLike> like)
206232
_like = Ensure.IsNotNull(like, nameof(like)).ToArray();
207233
}
208234

209-
private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry)
235+
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext)
210236
{
211237
var likeSerializer = typeof(TLike) switch
212238
{
213239
var t when t == typeof(BsonDocument) => null,
214-
var t when t == typeof(TDocument) => (IBsonSerializer<TLike>)documentSerializer,
215-
_ => serializerRegistry.GetSerializer<TLike>()
240+
var t when t == typeof(TDocument) => (IBsonSerializer<TLike>)renderContext.DocumentSerializer,
241+
_ => renderContext.SerializerRegistry.GetSerializer<TLike>()
216242
};
217243

218244
return new("like", new BsonArray(_like.Select(document => document.ToBsonDocument(likeSerializer))));
@@ -235,7 +261,7 @@ public NearSearchDefinition(
235261
_pivot = pivot;
236262
}
237263

238-
private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
264+
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
239265
new()
240266
{
241267
{ "origin", _origin },
@@ -259,7 +285,7 @@ public PhraseSearchDefinition(
259285
_slop = slop;
260286
}
261287

262-
private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
288+
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
263289
new()
264290
{
265291
{ "query", _query.Render() },
@@ -269,20 +295,20 @@ private protected override BsonDocument RenderArguments(IBsonSerializer<TDocumen
269295

270296
internal sealed class QueryStringSearchDefinition<TDocument> : OperatorSearchDefinition<TDocument>
271297
{
272-
private readonly FieldDefinition<TDocument> _defaultPath;
298+
private readonly SingleSearchPathDefinition<TDocument> _defaultPath;
273299
private readonly string _query;
274300

275301
public QueryStringSearchDefinition(FieldDefinition<TDocument> defaultPath, string query, SearchScoreDefinition<TDocument> score)
276302
: base(OperatorType.QueryString, score)
277303
{
278-
_defaultPath = Ensure.IsNotNull(defaultPath, nameof(defaultPath));
304+
_defaultPath = new SingleSearchPathDefinition<TDocument>(defaultPath);
279305
_query = Ensure.IsNotNull(query, nameof(query));
280306
}
281307

282-
private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
308+
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
283309
new()
284310
{
285-
{ "defaultPath", _defaultPath.Render(documentSerializer, serializerRegistry).FieldName },
311+
{ "defaultPath", _defaultPath.Render(renderContext) },
286312
{ "query", _query }
287313
};
288314
}
@@ -305,7 +331,7 @@ public RangeSearchDefinition(
305331
_max = ToBsonValue(_range.Max);
306332
}
307333

308-
private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
334+
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
309335
new()
310336
{
311337
{ _range.IsMinInclusive ? "gte" : "gt", _min, _min != null },
@@ -347,7 +373,7 @@ public RegexSearchDefinition(
347373
_allowAnalyzedField = allowAnalyzedField;
348374
}
349375

350-
private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
376+
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
351377
new()
352378
{
353379
{ "query", _query.Render() },
@@ -365,8 +391,8 @@ public SpanSearchDefinition(SearchSpanDefinition<TDocument> clause)
365391
_clause = Ensure.IsNotNull(clause, nameof(clause));
366392
}
367393

368-
private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
369-
_clause.Render(documentSerializer, serializerRegistry);
394+
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
395+
_clause.Render(renderContext);
370396
}
371397

372398
internal sealed class TextSearchDefinition<TDocument> : OperatorSearchDefinition<TDocument>
@@ -385,7 +411,7 @@ public TextSearchDefinition(
385411
_fuzzy = fuzzy;
386412
}
387413

388-
private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
414+
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
389415
new()
390416
{
391417
{ "query", _query.Render() },
@@ -409,7 +435,7 @@ public WildcardSearchDefinition(
409435
_allowAnalyzedField = allowAnalyzedField;
410436
}
411437

412-
private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
438+
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
413439
new()
414440
{
415441
{ "query", _query.Render() },

src/MongoDB.Driver/Search/SearchDefinition.cs

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
*/
1515

1616
using MongoDB.Bson;
17-
using MongoDB.Bson.Serialization;
1817
using MongoDB.Driver.Core.Misc;
1918

2019
namespace MongoDB.Driver.Search
@@ -26,12 +25,13 @@ namespace MongoDB.Driver.Search
2625
public abstract class SearchDefinition<TDocument>
2726
{
2827
/// <summary>
29-
/// Renders the search definition to a <see cref="BsonDocument"/>.
28+
/// Renders the search definition to a <see cref="BsonDocument" />.
3029
/// </summary>
31-
/// <param name="documentSerializer">The document serializer.</param>
32-
/// <param name="serializerRegistry">The serializer registry.</param>
33-
/// <returns>A <see cref="BsonDocument"/>.</returns>
34-
public abstract BsonDocument Render(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry);
30+
/// <param name="renderContext">The render context.</param>
31+
/// <returns>
32+
/// A <see cref="BsonDocument" />.
33+
/// </returns>
34+
public abstract BsonDocument Render(SearchDefinitionRenderContext<TDocument> renderContext);
3535

3636
/// <summary>
3737
/// Performs an implicit conversion from a BSON document to a <see cref="SearchDefinition{TDocument}"/>.
@@ -75,7 +75,7 @@ public BsonDocumentSearchDefinition(BsonDocument document)
7575
public BsonDocument Document { get; private set; }
7676

7777
/// <inheritdoc />
78-
public override BsonDocument Render(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
78+
public override BsonDocument Render(SearchDefinitionRenderContext<TDocument> renderContext) =>
7979
Document;
8080
}
8181

@@ -100,7 +100,7 @@ public JsonSearchDefinition(string json)
100100
public string Json { get; private set; }
101101

102102
/// <inheritdoc />
103-
public override BsonDocument Render(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
103+
public override BsonDocument Render(SearchDefinitionRenderContext<TDocument> renderContext) =>
104104
BsonDocument.Parse(Json);
105105
}
106106

@@ -131,8 +131,8 @@ private protected enum OperatorType
131131

132132
private readonly OperatorType _operatorType;
133133
// _path and _score used by many but not all subclasses
134-
private readonly SearchPathDefinition<TDocument> _path;
135-
private readonly SearchScoreDefinition<TDocument> _score;
134+
protected readonly SearchPathDefinition<TDocument> _path;
135+
protected readonly SearchScoreDefinition<TDocument> _score;
136136

137137
private protected OperatorSearchDefinition(OperatorType operatorType)
138138
: this(operatorType, null)
@@ -152,15 +152,16 @@ private protected OperatorSearchDefinition(OperatorType operatorType, SearchPath
152152
_score = score;
153153
}
154154

155-
public sealed override BsonDocument Render(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry)
155+
/// <inheritdoc />
156+
public sealed override BsonDocument Render(SearchDefinitionRenderContext<TDocument> renderContext)
156157
{
157-
var renderedArgs = RenderArguments(documentSerializer, serializerRegistry);
158-
renderedArgs.Add("path", () => _path.Render(documentSerializer, serializerRegistry), _path != null);
159-
renderedArgs.Add("score", () => _score.Render(documentSerializer, serializerRegistry), _score != null);
158+
var renderedArgs = RenderArguments(renderContext);
159+
renderedArgs.Add("path", () => _path.Render(renderContext), _path != null);
160+
renderedArgs.Add("score", () => _score.Render(renderContext), _score != null);
160161

161162
return new(_operatorType.ToCamelCase(), renderedArgs);
162163
}
163164

164-
private protected virtual BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) => new();
165+
private protected virtual BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) => new();
165166
}
166167
}

src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,41 @@ public SearchDefinition<TDocument> Autocomplete<TField>(
7272
public CompoundSearchDefinitionBuilder<TDocument> Compound(SearchScoreDefinition<TDocument> score = null) =>
7373
new CompoundSearchDefinitionBuilder<TDocument>(score);
7474

75+
/// <summary>
76+
/// Creates a search definition that performs a search for documents where
77+
/// the specified query <paramref name="operator"/> is satisfied from a single element
78+
/// of an array of embedded documents specified by <paramref name="path"/>.
79+
/// </summary>
80+
/// <param name="path">The indexed field to search.</param>
81+
/// <param name="operator">The operator.</param>
82+
/// <param name="score">The score modifier.</param>
83+
/// <returns>
84+
/// An embeddedDocument search definition.
85+
/// </returns>
86+
public SearchDefinition<TDocument> EmbeddedDocument<TField>(
87+
FieldDefinition<TDocument, IEnumerable<TField>> path,
88+
SearchDefinition<TField> @operator,
89+
SearchScoreDefinition<TDocument> score = null) =>
90+
new EmbeddedDocumentSearchDefinition<TDocument, TField>(path, @operator, score);
91+
92+
/// <summary>
93+
/// Creates a search definition that performs a search for documents where
94+
/// the specified query <paramref name="operator"/> is satisfied from a single element
95+
/// of an array of embedded documents specified by <paramref name="path"/>.
96+
/// </summary>
97+
/// <typeparam name="TField">The type of the field.</typeparam>
98+
/// <param name="path">The indexed field to search.</param>
99+
/// <param name="operator">The operator.</param>
100+
/// <param name="score">The score modifier.</param>
101+
/// <returns>
102+
/// An embeddedDocument search definition.
103+
/// </returns>
104+
public SearchDefinition<TDocument> EmbeddedDocument<TField>(
105+
Expression<Func<TDocument, IEnumerable<TField>>> path,
106+
SearchDefinition<TField> @operator,
107+
SearchScoreDefinition<TDocument> score = null) =>
108+
EmbeddedDocument(new ExpressionFieldDefinition<TDocument, IEnumerable<TField>>(path), @operator, score);
109+
75110
/// <summary>
76111
/// Creates a search definition that queries for documents where an indexed field is equal
77112
/// to the specified value.

0 commit comments

Comments
 (0)