Skip to content

Commit

Permalink
CSHARP-4477: Implement embeddedDocuments operator in Atlas Search (#1155
Browse files Browse the repository at this point in the history
)
  • Loading branch information
BorisDog committed Aug 9, 2023
1 parent 5412f33 commit 407dbac
Show file tree
Hide file tree
Showing 22 changed files with 422 additions and 134 deletions.
7 changes: 4 additions & 3 deletions src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1364,8 +1364,9 @@ public static class PipelineStageDefinitionBuilder
operatorName,
(s, sr, linqProvider) =>
{
var renderedSearchDefinition = searchDefinition.Render(s, sr);
renderedSearchDefinition.Add("highlight", () => searchOptions.Highlight.Render(s, sr), searchOptions.Highlight != null);
var renderContext = new SearchDefinitionRenderContext<TInput>(s, sr);
var renderedSearchDefinition = searchDefinition.Render(renderContext);
renderedSearchDefinition.Add("highlight", () => searchOptions.Highlight.Render(renderContext), searchOptions.Highlight != null);
renderedSearchDefinition.Add("count", () => searchOptions.CountOptions.Render(), searchOptions.CountOptions != null);
renderedSearchDefinition.Add("sort", () => searchOptions.Sort.Render(s, sr), searchOptions.Sort != null);
renderedSearchDefinition.Add("index", searchOptions.IndexName, searchOptions.IndexName != null);
Expand Down Expand Up @@ -1400,7 +1401,7 @@ public static class PipelineStageDefinitionBuilder
operatorName,
(s, sr, linqProvider) =>
{
var renderedSearchDefinition = searchDefinition.Render(s, sr);
var renderedSearchDefinition = searchDefinition.Render(new(s, sr));
renderedSearchDefinition.Add("count", () => count.Render(), count != null);
renderedSearchDefinition.Add("index", indexName, indexName != null);
Expand Down
74 changes: 50 additions & 24 deletions src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ internal sealed class AutocompleteSearchDefinition<TDocument> : OperatorSearchDe
_fuzzy = fuzzy;
}

private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
new()
{
{ "query", _query.Render() },
Expand Down Expand Up @@ -76,7 +76,7 @@ internal sealed class CompoundSearchDefinition<TDocument> : OperatorSearchDefini
_minimumShouldMatch = minimumShouldMatch;
}

private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry)
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext)
{
return new()
{
Expand All @@ -88,7 +88,33 @@ private protected override BsonDocument RenderArguments(IBsonSerializer<TDocumen
};

Func<BsonArray> Render(List<SearchDefinition<TDocument>> searchDefinitions) =>
() => new BsonArray(searchDefinitions.Select(clause => clause.Render(documentSerializer, serializerRegistry)));
() => new BsonArray(searchDefinitions.Select(clause => clause.Render(renderContext)));
}
}

internal sealed class EmbeddedDocumentSearchDefinition<TDocument, TField> : OperatorSearchDefinition<TDocument>
{
private readonly SearchDefinition<TField> _operator;

public EmbeddedDocumentSearchDefinition(FieldDefinition<TDocument, IEnumerable<TField>> path, SearchDefinition<TField> @operator, SearchScoreDefinition<TDocument> score)
: base(OperatorType.EmbeddedDocument,
new SingleSearchPathDefinition<TDocument>(path),
score)
{
_operator = Ensure.IsNotNull(@operator, nameof(@operator));
}

private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext)
{
// Add base path to all nested operator paths
var pathPrefix = _path.Render(renderContext).AsString;

var newRenderContext = new SearchDefinitionRenderContext<TField>(
renderContext.SerializerRegistry.GetSerializer<TField>(),
renderContext.SerializerRegistry,
pathPrefix);

return new("operator", _operator.Render(newRenderContext));
}
}

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

private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
new("value", _value);

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

private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
new()
{
{ "operator", _operator.Render(documentSerializer, serializerRegistry) },
{ "facets", new BsonDocument(_facets.Select(f => new BsonElement(f.Name, f.Render(documentSerializer, serializerRegistry)))) }
{ "operator", _operator.Render(renderContext) },
{ "facets", new BsonDocument(_facets.Select(f => new BsonElement(f.Name, f.Render(renderContext)))) }
};
}

Expand All @@ -170,7 +196,7 @@ internal sealed class GeoShapeSearchDefinition<TDocument, TCoordinates> : Operat
_relation = relation;
}

private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
new()
{
{ "geometry", _geometry.ToBsonDocument() },
Expand All @@ -192,7 +218,7 @@ internal sealed class GeoWithinSearchDefinition<TDocument, TCoordinates> : Opera
_area = Ensure.IsNotNull(area, nameof(area));
}

private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
new(_area.Render());
}

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

private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry)
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext)
{
var likeSerializer = typeof(TLike) switch
{
var t when t == typeof(BsonDocument) => null,
var t when t == typeof(TDocument) => (IBsonSerializer<TLike>)documentSerializer,
_ => serializerRegistry.GetSerializer<TLike>()
var t when t == typeof(TDocument) => (IBsonSerializer<TLike>)renderContext.DocumentSerializer,
_ => renderContext.SerializerRegistry.GetSerializer<TLike>()
};

return new("like", new BsonArray(_like.Select(document => document.ToBsonDocument(likeSerializer))));
Expand All @@ -235,7 +261,7 @@ internal sealed class NearSearchDefinition<TDocument> : OperatorSearchDefinition
_pivot = pivot;
}

private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
new()
{
{ "origin", _origin },
Expand All @@ -259,7 +285,7 @@ internal sealed class PhraseSearchDefinition<TDocument> : OperatorSearchDefiniti
_slop = slop;
}

private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
new()
{
{ "query", _query.Render() },
Expand All @@ -269,20 +295,20 @@ internal sealed class PhraseSearchDefinition<TDocument> : OperatorSearchDefiniti

internal sealed class QueryStringSearchDefinition<TDocument> : OperatorSearchDefinition<TDocument>
{
private readonly FieldDefinition<TDocument> _defaultPath;
private readonly SingleSearchPathDefinition<TDocument> _defaultPath;
private readonly string _query;

public QueryStringSearchDefinition(FieldDefinition<TDocument> defaultPath, string query, SearchScoreDefinition<TDocument> score)
: base(OperatorType.QueryString, score)
{
_defaultPath = Ensure.IsNotNull(defaultPath, nameof(defaultPath));
_defaultPath = new SingleSearchPathDefinition<TDocument>(defaultPath);
_query = Ensure.IsNotNull(query, nameof(query));
}

private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
new()
{
{ "defaultPath", _defaultPath.Render(documentSerializer, serializerRegistry).FieldName },
{ "defaultPath", _defaultPath.Render(renderContext) },
{ "query", _query }
};
}
Expand All @@ -305,7 +331,7 @@ internal sealed class RangeSearchDefinition<TDocument, TField> : OperatorSearchD
_max = ToBsonValue(_range.Max);
}

private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
new()
{
{ _range.IsMinInclusive ? "gte" : "gt", _min, _min != null },
Expand Down Expand Up @@ -347,7 +373,7 @@ internal sealed class RegexSearchDefinition<TDocument> : OperatorSearchDefinitio
_allowAnalyzedField = allowAnalyzedField;
}

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

private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
_clause.Render(documentSerializer, serializerRegistry);
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
_clause.Render(renderContext);
}

internal sealed class TextSearchDefinition<TDocument> : OperatorSearchDefinition<TDocument>
Expand All @@ -385,7 +411,7 @@ internal sealed class TextSearchDefinition<TDocument> : OperatorSearchDefinition
_fuzzy = fuzzy;
}

private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
new()
{
{ "query", _query.Render() },
Expand All @@ -409,7 +435,7 @@ internal sealed class WildcardSearchDefinition<TDocument> : OperatorSearchDefini
_allowAnalyzedField = allowAnalyzedField;
}

private protected override BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) =>
new()
{
{ "query", _query.Render() },
Expand Down
31 changes: 16 additions & 15 deletions src/MongoDB.Driver/Search/SearchDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
*/

using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver.Core.Misc;

namespace MongoDB.Driver.Search
Expand All @@ -26,12 +25,13 @@ namespace MongoDB.Driver.Search
public abstract class SearchDefinition<TDocument>
{
/// <summary>
/// Renders the search definition to a <see cref="BsonDocument"/>.
/// Renders the search definition to a <see cref="BsonDocument" />.
/// </summary>
/// <param name="documentSerializer">The document serializer.</param>
/// <param name="serializerRegistry">The serializer registry.</param>
/// <returns>A <see cref="BsonDocument"/>.</returns>
public abstract BsonDocument Render(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry);
/// <param name="renderContext">The render context.</param>
/// <returns>
/// A <see cref="BsonDocument" />.
/// </returns>
public abstract BsonDocument Render(SearchDefinitionRenderContext<TDocument> renderContext);

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

/// <inheritdoc />
public override BsonDocument Render(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
public override BsonDocument Render(SearchDefinitionRenderContext<TDocument> renderContext) =>
Document;
}

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

/// <inheritdoc />
public override BsonDocument Render(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) =>
public override BsonDocument Render(SearchDefinitionRenderContext<TDocument> renderContext) =>
BsonDocument.Parse(Json);
}

Expand Down Expand Up @@ -131,8 +131,8 @@ private protected enum OperatorType

private readonly OperatorType _operatorType;
// _path and _score used by many but not all subclasses
private readonly SearchPathDefinition<TDocument> _path;
private readonly SearchScoreDefinition<TDocument> _score;
protected readonly SearchPathDefinition<TDocument> _path;
protected readonly SearchScoreDefinition<TDocument> _score;

private protected OperatorSearchDefinition(OperatorType operatorType)
: this(operatorType, null)
Expand All @@ -152,15 +152,16 @@ private protected OperatorSearchDefinition(OperatorType operatorType, SearchPath
_score = score;
}

public sealed override BsonDocument Render(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry)
/// <inheritdoc />
public sealed override BsonDocument Render(SearchDefinitionRenderContext<TDocument> renderContext)
{
var renderedArgs = RenderArguments(documentSerializer, serializerRegistry);
renderedArgs.Add("path", () => _path.Render(documentSerializer, serializerRegistry), _path != null);
renderedArgs.Add("score", () => _score.Render(documentSerializer, serializerRegistry), _score != null);
var renderedArgs = RenderArguments(renderContext);
renderedArgs.Add("path", () => _path.Render(renderContext), _path != null);
renderedArgs.Add("score", () => _score.Render(renderContext), _score != null);

return new(_operatorType.ToCamelCase(), renderedArgs);
}

private protected virtual BsonDocument RenderArguments(IBsonSerializer<TDocument> documentSerializer, IBsonSerializerRegistry serializerRegistry) => new();
private protected virtual BsonDocument RenderArguments(SearchDefinitionRenderContext<TDocument> renderContext) => new();
}
}
35 changes: 35 additions & 0 deletions src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,41 @@ public sealed class SearchDefinitionBuilder<TDocument>
public CompoundSearchDefinitionBuilder<TDocument> Compound(SearchScoreDefinition<TDocument> score = null) =>
new CompoundSearchDefinitionBuilder<TDocument>(score);

/// <summary>
/// Creates a search definition that performs a search for documents where
/// the specified query <paramref name="operator"/> is satisfied from a single element
/// of an array of embedded documents specified by <paramref name="path"/>.
/// </summary>
/// <param name="path">The indexed field to search.</param>
/// <param name="operator">The operator.</param>
/// <param name="score">The score modifier.</param>
/// <returns>
/// An embeddedDocument search definition.
/// </returns>
public SearchDefinition<TDocument> EmbeddedDocument<TField>(
FieldDefinition<TDocument, IEnumerable<TField>> path,
SearchDefinition<TField> @operator,
SearchScoreDefinition<TDocument> score = null) =>
new EmbeddedDocumentSearchDefinition<TDocument, TField>(path, @operator, score);

/// <summary>
/// Creates a search definition that performs a search for documents where
/// the specified query <paramref name="operator"/> is satisfied from a single element
/// of an array of embedded documents specified by <paramref name="path"/>.
/// </summary>
/// <typeparam name="TField">The type of the field.</typeparam>
/// <param name="path">The indexed field to search.</param>
/// <param name="operator">The operator.</param>
/// <param name="score">The score modifier.</param>
/// <returns>
/// An embeddedDocument search definition.
/// </returns>
public SearchDefinition<TDocument> EmbeddedDocument<TField>(
Expression<Func<TDocument, IEnumerable<TField>>> path,
SearchDefinition<TField> @operator,
SearchScoreDefinition<TDocument> score = null) =>
EmbeddedDocument(new ExpressionFieldDefinition<TDocument, IEnumerable<TField>>(path), @operator, score);

/// <summary>
/// Creates a search definition that queries for documents where an indexed field is equal
/// to the specified value.
Expand Down

0 comments on commit 407dbac

Please sign in to comment.