From 407dbacd9e7dc490938fc945a0348bf936088626 Mon Sep 17 00:00:00 2001 From: BorisDog Date: Wed, 9 Aug 2023 12:48:06 -0700 Subject: [PATCH] CSHARP-4477: Implement embeddedDocuments operator in Atlas Search (#1155) --- .../PipelineStageDefinitionBuilder.cs | 7 +- .../Search/OperatorSearchDefinitions.cs | 74 +++++++++++----- src/MongoDB.Driver/Search/SearchDefinition.cs | 31 +++---- .../Search/SearchDefinitionBuilder.cs | 35 ++++++++ .../Search/SearchDefinitionRenderContext.cs | 43 +++++++++ src/MongoDB.Driver/Search/SearchFacet.cs | 8 +- .../Search/SearchFacetBuilder.cs | 13 ++- .../Search/SearchHighlightOptions.cs | 10 +-- .../Search/SearchPathDefinition.cs | 21 ++++- .../Search/SearchPathDefinitionBuilder.cs | 23 ++--- .../Search/SearchScoreDefinition.cs | 6 +- .../Search/SearchScoreDefinitionBuilder.cs | 13 ++- .../Search/SearchScoreFunction.cs | 8 +- .../Search/SearchScoreFunctionBuilder.cs | 21 +++-- .../Search/SearchSpanDefinition.cs | 10 +-- .../Search/SearchSpanDefinitionBuilder.cs | 24 ++--- .../Search/AtlasSearchTests.cs | 41 +++++++++ .../Search/SearchDefinitionBuilderTests.cs | 87 ++++++++++++++++++- .../Search/SearchFacetBuilderTests.cs | 2 +- .../SearchPathDefinitionBuilderTests.cs | 72 ++++++++++++++- .../SearchScoreDefinitionBuilderTests.cs | 5 +- .../SearchSpanDefinitionBuilderTests.cs | 2 +- 22 files changed, 422 insertions(+), 134 deletions(-) create mode 100644 src/MongoDB.Driver/Search/SearchDefinitionRenderContext.cs diff --git a/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs b/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs index 68f9fd93f9b..23912b7f709 100644 --- a/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs +++ b/src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs @@ -1364,8 +1364,9 @@ public static PipelineStageDefinition Search( 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(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); @@ -1400,7 +1401,7 @@ public static PipelineStageDefinition SearchMeta { - 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); diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index 792127e9400..cf723dcd0fc 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -42,7 +42,7 @@ public AutocompleteSearchDefinition( _fuzzy = fuzzy; } - private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext renderContext) => new() { { "query", _query.Render() }, @@ -76,7 +76,7 @@ public CompoundSearchDefinition( _minimumShouldMatch = minimumShouldMatch; } - private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) + private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext renderContext) { return new() { @@ -88,7 +88,33 @@ private protected override BsonDocument RenderArguments(IBsonSerializer Render(List> searchDefinitions) => - () => new BsonArray(searchDefinitions.Select(clause => clause.Render(documentSerializer, serializerRegistry))); + () => new BsonArray(searchDefinitions.Select(clause => clause.Render(renderContext))); + } + } + + internal sealed class EmbeddedDocumentSearchDefinition : OperatorSearchDefinition + { + private readonly SearchDefinition _operator; + + public EmbeddedDocumentSearchDefinition(FieldDefinition> path, SearchDefinition @operator, SearchScoreDefinition score) + : base(OperatorType.EmbeddedDocument, + new SingleSearchPathDefinition(path), + score) + { + _operator = Ensure.IsNotNull(@operator, nameof(@operator)); + } + + private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext renderContext) + { + // Add base path to all nested operator paths + var pathPrefix = _path.Render(renderContext).AsString; + + var newRenderContext = new SearchDefinitionRenderContext( + renderContext.SerializerRegistry.GetSerializer(), + renderContext.SerializerRegistry, + pathPrefix); + + return new("operator", _operator.Render(newRenderContext)); } } @@ -102,7 +128,7 @@ public EqualsSearchDefinition(FieldDefinition path, TField value, Sea _value = ToBsonValue(value); } - private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext renderContext) => new("value", _value); private static BsonValue ToBsonValue(TField value) => @@ -145,11 +171,11 @@ public FacetSearchDefinition(SearchDefinition @operator, IEnumerable< _facets = Ensure.IsNotNull(facets, nameof(facets)).ToArray(); } - private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext 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)))) } }; } @@ -170,7 +196,7 @@ public GeoShapeSearchDefinition( _relation = relation; } - private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext renderContext) => new() { { "geometry", _geometry.ToBsonDocument() }, @@ -192,7 +218,7 @@ public GeoWithinSearchDefinition( _area = Ensure.IsNotNull(area, nameof(area)); } - private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext renderContext) => new(_area.Render()); } @@ -206,13 +232,13 @@ public MoreLikeThisSearchDefinition(IEnumerable like) _like = Ensure.IsNotNull(like, nameof(like)).ToArray(); } - private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) + private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext renderContext) { var likeSerializer = typeof(TLike) switch { var t when t == typeof(BsonDocument) => null, - var t when t == typeof(TDocument) => (IBsonSerializer)documentSerializer, - _ => serializerRegistry.GetSerializer() + var t when t == typeof(TDocument) => (IBsonSerializer)renderContext.DocumentSerializer, + _ => renderContext.SerializerRegistry.GetSerializer() }; return new("like", new BsonArray(_like.Select(document => document.ToBsonDocument(likeSerializer)))); @@ -235,7 +261,7 @@ public NearSearchDefinition( _pivot = pivot; } - private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext renderContext) => new() { { "origin", _origin }, @@ -259,7 +285,7 @@ public PhraseSearchDefinition( _slop = slop; } - private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext renderContext) => new() { { "query", _query.Render() }, @@ -269,20 +295,20 @@ private protected override BsonDocument RenderArguments(IBsonSerializer : OperatorSearchDefinition { - private readonly FieldDefinition _defaultPath; + private readonly SingleSearchPathDefinition _defaultPath; private readonly string _query; public QueryStringSearchDefinition(FieldDefinition defaultPath, string query, SearchScoreDefinition score) : base(OperatorType.QueryString, score) { - _defaultPath = Ensure.IsNotNull(defaultPath, nameof(defaultPath)); + _defaultPath = new SingleSearchPathDefinition(defaultPath); _query = Ensure.IsNotNull(query, nameof(query)); } - private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext renderContext) => new() { - { "defaultPath", _defaultPath.Render(documentSerializer, serializerRegistry).FieldName }, + { "defaultPath", _defaultPath.Render(renderContext) }, { "query", _query } }; } @@ -305,7 +331,7 @@ public RangeSearchDefinition( _max = ToBsonValue(_range.Max); } - private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext renderContext) => new() { { _range.IsMinInclusive ? "gte" : "gt", _min, _min != null }, @@ -347,7 +373,7 @@ public RegexSearchDefinition( _allowAnalyzedField = allowAnalyzedField; } - private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext renderContext) => new() { { "query", _query.Render() }, @@ -365,8 +391,8 @@ public SpanSearchDefinition(SearchSpanDefinition clause) _clause = Ensure.IsNotNull(clause, nameof(clause)); } - private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => - _clause.Render(documentSerializer, serializerRegistry); + private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext renderContext) => + _clause.Render(renderContext); } internal sealed class TextSearchDefinition : OperatorSearchDefinition @@ -385,7 +411,7 @@ public TextSearchDefinition( _fuzzy = fuzzy; } - private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext renderContext) => new() { { "query", _query.Render() }, @@ -409,7 +435,7 @@ public WildcardSearchDefinition( _allowAnalyzedField = allowAnalyzedField; } - private protected override BsonDocument RenderArguments(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + private protected override BsonDocument RenderArguments(SearchDefinitionRenderContext renderContext) => new() { { "query", _query.Render() }, diff --git a/src/MongoDB.Driver/Search/SearchDefinition.cs b/src/MongoDB.Driver/Search/SearchDefinition.cs index a8bc26686e4..ea4a3fe1df1 100644 --- a/src/MongoDB.Driver/Search/SearchDefinition.cs +++ b/src/MongoDB.Driver/Search/SearchDefinition.cs @@ -14,7 +14,6 @@ */ using MongoDB.Bson; -using MongoDB.Bson.Serialization; using MongoDB.Driver.Core.Misc; namespace MongoDB.Driver.Search @@ -26,12 +25,13 @@ namespace MongoDB.Driver.Search public abstract class SearchDefinition { /// - /// Renders the search definition to a . + /// Renders the search definition to a . /// - /// The document serializer. - /// The serializer registry. - /// A . - public abstract BsonDocument Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry); + /// The render context. + /// + /// A . + /// + public abstract BsonDocument Render(SearchDefinitionRenderContext renderContext); /// /// Performs an implicit conversion from a BSON document to a . @@ -75,7 +75,7 @@ public BsonDocumentSearchDefinition(BsonDocument document) public BsonDocument Document { get; private set; } /// - public override BsonDocument Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + public override BsonDocument Render(SearchDefinitionRenderContext renderContext) => Document; } @@ -100,7 +100,7 @@ public JsonSearchDefinition(string json) public string Json { get; private set; } /// - public override BsonDocument Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + public override BsonDocument Render(SearchDefinitionRenderContext renderContext) => BsonDocument.Parse(Json); } @@ -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 _path; - private readonly SearchScoreDefinition _score; + protected readonly SearchPathDefinition _path; + protected readonly SearchScoreDefinition _score; private protected OperatorSearchDefinition(OperatorType operatorType) : this(operatorType, null) @@ -152,15 +152,16 @@ private protected OperatorSearchDefinition(OperatorType operatorType, SearchPath _score = score; } - public sealed override BsonDocument Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) + /// + public sealed override BsonDocument Render(SearchDefinitionRenderContext 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 documentSerializer, IBsonSerializerRegistry serializerRegistry) => new(); + private protected virtual BsonDocument RenderArguments(SearchDefinitionRenderContext renderContext) => new(); } } diff --git a/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs b/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs index 0aeecde88c0..d652068ce75 100644 --- a/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs +++ b/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs @@ -72,6 +72,41 @@ public SearchDefinition Autocomplete( public CompoundSearchDefinitionBuilder Compound(SearchScoreDefinition score = null) => new CompoundSearchDefinitionBuilder(score); + /// + /// Creates a search definition that performs a search for documents where + /// the specified query is satisfied from a single element + /// of an array of embedded documents specified by . + /// + /// The indexed field to search. + /// The operator. + /// The score modifier. + /// + /// An embeddedDocument search definition. + /// + public SearchDefinition EmbeddedDocument( + FieldDefinition> path, + SearchDefinition @operator, + SearchScoreDefinition score = null) => + new EmbeddedDocumentSearchDefinition(path, @operator, score); + + /// + /// Creates a search definition that performs a search for documents where + /// the specified query is satisfied from a single element + /// of an array of embedded documents specified by . + /// + /// The type of the field. + /// The indexed field to search. + /// The operator. + /// The score modifier. + /// + /// An embeddedDocument search definition. + /// + public SearchDefinition EmbeddedDocument( + Expression>> path, + SearchDefinition @operator, + SearchScoreDefinition score = null) => + EmbeddedDocument(new ExpressionFieldDefinition>(path), @operator, score); + /// /// Creates a search definition that queries for documents where an indexed field is equal /// to the specified value. diff --git a/src/MongoDB.Driver/Search/SearchDefinitionRenderContext.cs b/src/MongoDB.Driver/Search/SearchDefinitionRenderContext.cs new file mode 100644 index 00000000000..9a7ba01ae43 --- /dev/null +++ b/src/MongoDB.Driver/Search/SearchDefinitionRenderContext.cs @@ -0,0 +1,43 @@ +using MongoDB.Bson.Serialization; +using MongoDB.Driver.Core.Misc; + +namespace MongoDB.Driver.Search +{ + /// + /// Encapsulates classes needed for rendering Search definitions. + /// + /// The type of the document. + public sealed class SearchDefinitionRenderContext + { + /// + /// Initializes a new instance of the class. + /// + /// The document serializer. + /// The serializer registry. + /// The path prefix. + public SearchDefinitionRenderContext( + IBsonSerializer documentSerializer, + IBsonSerializerRegistry serializerRegistry, + string pathPrefix = null) + { + DocumentSerializer = Ensure.IsNotNull(documentSerializer, nameof(documentSerializer)); + PathPrefix = pathPrefix; + SerializerRegistry = Ensure.IsNotNull(serializerRegistry, nameof(serializerRegistry)); + } + + /// + /// Gets the document serializer. + /// + public IBsonSerializer DocumentSerializer { get; } + + /// + /// Gets the path prefix. + /// + public string PathPrefix { get; } + + /// + /// Gets the serializer registry. + /// + public IBsonSerializerRegistry SerializerRegistry { get; } + } +} diff --git a/src/MongoDB.Driver/Search/SearchFacet.cs b/src/MongoDB.Driver/Search/SearchFacet.cs index 4cd134b04d4..800f06c858b 100644 --- a/src/MongoDB.Driver/Search/SearchFacet.cs +++ b/src/MongoDB.Driver/Search/SearchFacet.cs @@ -14,7 +14,6 @@ */ using MongoDB.Bson; -using MongoDB.Bson.Serialization; namespace MongoDB.Driver.Search { @@ -41,9 +40,8 @@ protected SearchFacet(string name) /// /// Renders the search facet to a . /// - /// The document serializer. - /// The serializer registry. - /// A . - public abstract BsonDocument Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry); + /// The render context. + /// A . + public abstract BsonDocument Render(SearchDefinitionRenderContext renderContext); } } diff --git a/src/MongoDB.Driver/Search/SearchFacetBuilder.cs b/src/MongoDB.Driver/Search/SearchFacetBuilder.cs index 70472c6e280..2d7bf1cb8e5 100644 --- a/src/MongoDB.Driver/Search/SearchFacetBuilder.cs +++ b/src/MongoDB.Driver/Search/SearchFacetBuilder.cs @@ -18,7 +18,6 @@ using System.Linq; using System.Linq.Expressions; using MongoDB.Bson; -using MongoDB.Bson.Serialization; using MongoDB.Driver.Core.Misc; namespace MongoDB.Driver.Search @@ -219,11 +218,11 @@ public DateSearchFacet(string name, SearchPathDefinition path, IEnume _default = @default; } - public override BsonDocument Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + public override BsonDocument Render(SearchDefinitionRenderContext renderContext) => new() { { "type", "date" }, - { "path", _path.Render(documentSerializer, serializerRegistry) }, + { "path", _path.Render(renderContext) }, { "boundaries", new BsonArray(_boundaries) }, { "default", _default, _default != null } }; @@ -243,11 +242,11 @@ public NumberSearchFacet(string name, SearchPathDefinition path, IEnu _default = @default; } - public override BsonDocument Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + public override BsonDocument Render(SearchDefinitionRenderContext renderContext) => new() { { "type", "number" }, - { "path", _path.Render(documentSerializer, serializerRegistry) }, + { "path", _path.Render(renderContext) }, { "boundaries", new BsonArray(_boundaries) }, { "default", _default, _default != null } }; @@ -265,11 +264,11 @@ public StringSearchFacet(string name, SearchPathDefinition path, int? _numBuckets = Ensure.IsNullOrBetween(numBuckets, 1, 1000, nameof(numBuckets)); } - public override BsonDocument Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + public override BsonDocument Render(SearchDefinitionRenderContext renderContext) => new() { { "type", "string" }, - { "path", _path.Render(documentSerializer, serializerRegistry) }, + { "path", _path.Render(renderContext) }, { "numBuckets", _numBuckets, _numBuckets != null } }; } diff --git a/src/MongoDB.Driver/Search/SearchHighlightOptions.cs b/src/MongoDB.Driver/Search/SearchHighlightOptions.cs index ec4026742fb..4dfa49475a8 100644 --- a/src/MongoDB.Driver/Search/SearchHighlightOptions.cs +++ b/src/MongoDB.Driver/Search/SearchHighlightOptions.cs @@ -16,7 +16,6 @@ using System; using System.Linq.Expressions; using MongoDB.Bson; -using MongoDB.Bson.Serialization; using MongoDB.Driver.Core.Misc; namespace MongoDB.Driver.Search @@ -98,13 +97,12 @@ public SearchPathDefinition Path /// /// Renders the options to a . /// - /// The document serializer. - /// The serializer registry. - /// A . - public BsonDocument Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) + /// The render context. + /// A . + public BsonDocument Render(SearchDefinitionRenderContext renderContext) => new() { - { "path", _path.Render(documentSerializer, serializerRegistry) }, + { "path", _path.Render(renderContext) }, { "maxCharsToExamine", _maxCharsToExamine, _maxCharsToExamine != null}, { "maxNumPassages", _maxNumPassages, _maxNumPassages != null } }; diff --git a/src/MongoDB.Driver/Search/SearchPathDefinition.cs b/src/MongoDB.Driver/Search/SearchPathDefinition.cs index b4efc54e607..1085f19d7a3 100644 --- a/src/MongoDB.Driver/Search/SearchPathDefinition.cs +++ b/src/MongoDB.Driver/Search/SearchPathDefinition.cs @@ -16,7 +16,6 @@ using System.Collections.Generic; using System.Linq; using MongoDB.Bson; -using MongoDB.Bson.Serialization; namespace MongoDB.Driver.Search { @@ -29,10 +28,9 @@ public abstract class SearchPathDefinition /// /// Renders the path to a . /// - /// The document serializer. - /// The serializer registry. + /// The render context. /// A . - public abstract BsonValue Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry); + public abstract BsonValue Render(SearchDefinitionRenderContext renderContext); /// /// Performs an implicit conversion from to @@ -98,5 +96,20 @@ public static implicit operator SearchPathDefinition(string[] fieldNa /// public static implicit operator SearchPathDefinition(List fieldNames) => new MultiSearchPathDefinition(fieldNames.Select(fieldName => new StringFieldDefinition(fieldName))); + + /// + /// Renders the field. + /// + /// The field definition. + /// The render context. + /// The rendered field. + protected string RenderField(FieldDefinition fieldDefinition, SearchDefinitionRenderContext renderContext) + { + var renderedField = fieldDefinition.Render(renderContext.DocumentSerializer, renderContext.SerializerRegistry); + + return renderContext.PathPrefix == null ? + renderedField.FieldName : + $"{renderContext.PathPrefix}.{renderedField.FieldName}"; + } } } diff --git a/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs b/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs index 0537e13ce54..4b169bf9f85 100644 --- a/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs +++ b/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs @@ -18,7 +18,6 @@ using System.Linq; using System.Linq.Expressions; using MongoDB.Bson; -using MongoDB.Bson.Serialization; using MongoDB.Driver.Core.Misc; namespace MongoDB.Driver.Search @@ -113,10 +112,10 @@ public AnalyzerSearchPathDefinition(FieldDefinition field, string ana _analyzerName = Ensure.IsNotNull(analyzerName, nameof(analyzerName)); } - public override BsonValue Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + public override BsonValue Render(SearchDefinitionRenderContext renderContext) => new BsonDocument() { - { "value", _field.Render(documentSerializer, serializerRegistry).FieldName }, + { "value", RenderField(_field, renderContext) }, { "multi", _analyzerName } }; } @@ -130,8 +129,8 @@ public MultiSearchPathDefinition(IEnumerable> fields) _fields = Ensure.IsNotNull(fields, nameof(fields)).ToArray(); } - public override BsonValue Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => - new BsonArray(_fields.Select(field => field.Render(documentSerializer, serializerRegistry).FieldName)); + public override BsonValue Render(SearchDefinitionRenderContext renderContext) => + new BsonArray(_fields.Select(field => RenderField(field, renderContext))); } internal sealed class SingleSearchPathDefinition : SearchPathDefinition @@ -143,11 +142,8 @@ public SingleSearchPathDefinition(FieldDefinition field) _field = Ensure.IsNotNull(field, nameof(field)); } - public override BsonValue Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) - { - var renderedField = _field.Render(documentSerializer, serializerRegistry); - return new BsonString(renderedField.FieldName); - } + public override BsonValue Render(SearchDefinitionRenderContext renderContext) => + RenderField(_field, renderContext); } internal sealed class WildcardSearchPathDefinition : SearchPathDefinition @@ -159,10 +155,7 @@ public WildcardSearchPathDefinition(string query) _query = Ensure.IsNotNull(query, nameof(query)); } - public override BsonValue Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => - new BsonDocument() - { - { "wildcard", _query } - }; + public override BsonValue Render(SearchDefinitionRenderContext renderContext) => + new BsonDocument("wildcard", _query); } } diff --git a/src/MongoDB.Driver/Search/SearchScoreDefinition.cs b/src/MongoDB.Driver/Search/SearchScoreDefinition.cs index 5e94b79633e..b0317a4e410 100644 --- a/src/MongoDB.Driver/Search/SearchScoreDefinition.cs +++ b/src/MongoDB.Driver/Search/SearchScoreDefinition.cs @@ -14,7 +14,6 @@ */ using MongoDB.Bson; -using MongoDB.Bson.Serialization; namespace MongoDB.Driver.Search { @@ -27,9 +26,8 @@ public abstract class SearchScoreDefinition /// /// Renders the score modifier to a . /// - /// The document serializer. - /// The serializer registry. + /// The render context. /// A . - public abstract BsonDocument Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry); + public abstract BsonDocument Render(SearchDefinitionRenderContext renderContext); } } diff --git a/src/MongoDB.Driver/Search/SearchScoreDefinitionBuilder.cs b/src/MongoDB.Driver/Search/SearchScoreDefinitionBuilder.cs index cfcd33036a5..5c57a3eba3c 100644 --- a/src/MongoDB.Driver/Search/SearchScoreDefinitionBuilder.cs +++ b/src/MongoDB.Driver/Search/SearchScoreDefinitionBuilder.cs @@ -16,7 +16,6 @@ using System; using System.Linq.Expressions; using MongoDB.Bson; -using MongoDB.Bson.Serialization; using MongoDB.Driver.Core.Misc; namespace MongoDB.Driver.Search @@ -101,10 +100,10 @@ public BoostPathSearchScoreDefinition(SearchPathDefinition path, doub _undefined = undefined; } - public override BsonDocument Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + public override BsonDocument Render(SearchDefinitionRenderContext renderContext) => new("boost", new BsonDocument { - { "path", _path.Render(documentSerializer, serializerRegistry) }, + { "path", _path.Render(renderContext) }, { "undefined", _undefined, _undefined != 0 } }); } @@ -118,7 +117,7 @@ public BoostValueSearchScoreDefinition(double value) _value = Ensure.IsGreaterThanZero(value, nameof(value)); } - public override BsonDocument Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + public override BsonDocument Render(SearchDefinitionRenderContext renderContext) => new("boost", new BsonDocument("value", _value)); } @@ -131,7 +130,7 @@ public ConstantSearchScoreDefinition(double value) _value = Ensure.IsGreaterThanZero(value, nameof(value)); } - public override BsonDocument Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + public override BsonDocument Render(SearchDefinitionRenderContext renderContext) => new("constant", new BsonDocument("value", _value)); } @@ -144,7 +143,7 @@ public FunctionSearchScoreDefinition(SearchScoreFunction function) _function = Ensure.IsNotNull(function, nameof(function)); } - public override BsonDocument Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => - new("function", _function.Render(documentSerializer, serializerRegistry)); + public override BsonDocument Render(SearchDefinitionRenderContext renderContext) => + new("function", _function.Render(renderContext)); } } diff --git a/src/MongoDB.Driver/Search/SearchScoreFunction.cs b/src/MongoDB.Driver/Search/SearchScoreFunction.cs index e77ac9d153e..a1d4f7c37d2 100644 --- a/src/MongoDB.Driver/Search/SearchScoreFunction.cs +++ b/src/MongoDB.Driver/Search/SearchScoreFunction.cs @@ -14,7 +14,6 @@ */ using MongoDB.Bson; -using MongoDB.Bson.Serialization; namespace MongoDB.Driver.Search { @@ -27,9 +26,8 @@ public abstract class SearchScoreFunction /// /// Renders the score function to a . /// - /// The document serializer. - /// The serializer registry. - /// A . - public abstract BsonDocument Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry); + /// The render context. + /// A . + public abstract BsonDocument Render(SearchDefinitionRenderContext renderContext); } } diff --git a/src/MongoDB.Driver/Search/SearchScoreFunctionBuilder.cs b/src/MongoDB.Driver/Search/SearchScoreFunctionBuilder.cs index 10d08cf3fb3..d78e2062599 100644 --- a/src/MongoDB.Driver/Search/SearchScoreFunctionBuilder.cs +++ b/src/MongoDB.Driver/Search/SearchScoreFunctionBuilder.cs @@ -18,7 +18,6 @@ using System.Linq; using System.Linq.Expressions; using MongoDB.Bson; -using MongoDB.Bson.Serialization; using MongoDB.Driver.Core.Misc; namespace MongoDB.Driver.Search @@ -180,8 +179,8 @@ public ArithmeticSearchScoreFunction(string operatorName, IEnumerable documentSerializer, IBsonSerializerRegistry serializerRegister) => - new(_operatorName, new BsonArray(_operands.Select(o => o.Render(documentSerializer, serializerRegister)))); + public override BsonDocument Render(SearchDefinitionRenderContext renderContext) => + new(_operatorName, new BsonArray(_operands.Select(o => o.Render(renderContext)))); } internal sealed class ConstantSearchScoreFunction : SearchScoreFunction @@ -193,7 +192,7 @@ public ConstantSearchScoreFunction(double value) _value = value; } - public override BsonDocument Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegister) => + public override BsonDocument Render(SearchDefinitionRenderContext renderContext) => new("constant", _value); } @@ -219,10 +218,10 @@ public GaussSearchScoreFunction( _offset = offset; } - public override BsonDocument Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegister) => + public override BsonDocument Render(SearchDefinitionRenderContext renderContext) => new("gauss", new BsonDocument() { - { "path", _path.Render(documentSerializer, serializerRegister) }, + { "path", _path.Render(renderContext) }, { "origin", _origin }, { "scale", _scale }, { "decay", _decay, _decay != 0.5 }, @@ -241,9 +240,9 @@ public PathSearchScoreFunction(SearchPathDefinition path, double unde _undefined = undefined; } - public override BsonDocument Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegister) + public override BsonDocument Render(SearchDefinitionRenderContext renderContext) { - var renderedPath = _path.Render(documentSerializer, serializerRegister); + var renderedPath = _path.Render(renderContext); var pathDocument = _undefined == 0 ? renderedPath : new BsonDocument() { { "value", renderedPath }, @@ -256,7 +255,7 @@ public override BsonDocument Render(IBsonSerializer documentSerialize internal sealed class RelevanceSearchScoreFunction : SearchScoreFunction { - public override BsonDocument Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegister) => + public override BsonDocument Render(SearchDefinitionRenderContext renderContext) => new("score", "relevance"); } @@ -270,7 +269,7 @@ public UnarySearchScoreFunction(string operatorName, SearchScoreFunction documentSerializer, IBsonSerializerRegistry serializerRegister) => - new(_operatorName, _operand.Render(documentSerializer, serializerRegister)); + public override BsonDocument Render(SearchDefinitionRenderContext renderContext) => + new(_operatorName, _operand.Render(renderContext)); } } diff --git a/src/MongoDB.Driver/Search/SearchSpanDefinition.cs b/src/MongoDB.Driver/Search/SearchSpanDefinition.cs index 4a563c07009..1dff8efa373 100644 --- a/src/MongoDB.Driver/Search/SearchSpanDefinition.cs +++ b/src/MongoDB.Driver/Search/SearchSpanDefinition.cs @@ -14,7 +14,6 @@ */ using MongoDB.Bson; -using MongoDB.Bson.Serialization; namespace MongoDB.Driver.Search { @@ -47,12 +46,11 @@ private protected enum ClauseType /// /// Renders the span clause to a . /// - /// The document serializer. - /// The serializer registry. + /// The render context. /// A . - public BsonDocument Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => - new(_clauseType.ToCamelCase(), RenderClause(documentSerializer, serializerRegistry)); + public BsonDocument Render(SearchDefinitionRenderContext renderContext) => + new(_clauseType.ToCamelCase(), RenderClause(renderContext)); - private protected virtual BsonDocument RenderClause(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => new(); + private protected virtual BsonDocument RenderClause(SearchDefinitionRenderContext renderContext) => new(); } } diff --git a/src/MongoDB.Driver/Search/SearchSpanDefinitionBuilder.cs b/src/MongoDB.Driver/Search/SearchSpanDefinitionBuilder.cs index 93a6c65a542..fd693e1e195 100644 --- a/src/MongoDB.Driver/Search/SearchSpanDefinitionBuilder.cs +++ b/src/MongoDB.Driver/Search/SearchSpanDefinitionBuilder.cs @@ -18,7 +18,6 @@ using System.Linq; using System.Linq.Expressions; using MongoDB.Bson; -using MongoDB.Bson.Serialization; using MongoDB.Driver; using MongoDB.Driver.Core.Misc; @@ -112,10 +111,11 @@ public FirstSearchSpanDefinition(SearchSpanDefinition @operator, int _operator = Ensure.IsNotNull(@operator, nameof(@operator)); _endPositionLte = endPositionLte; } - private protected override BsonDocument RenderClause(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + + private protected override BsonDocument RenderClause(SearchDefinitionRenderContext renderContext) => new() { - { "operator", _operator.Render(documentSerializer, serializerRegistry) }, + { "operator", _operator.Render(renderContext) }, { "endPositionLte", _endPositionLte } }; } @@ -134,10 +134,10 @@ public NearSearchSpanDefinition(IEnumerable> cla _inOrder = inOrder; } - private protected override BsonDocument RenderClause(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + private protected override BsonDocument RenderClause(SearchDefinitionRenderContext renderContext) => new() { - { "clauses", new BsonArray(_clauses.Select(clause => clause.Render(documentSerializer, serializerRegistry))) }, + { "clauses", new BsonArray(_clauses.Select(clause => clause.Render(renderContext))) }, { "slop", _slop }, { "inOrder", _inOrder }, }; @@ -153,8 +153,8 @@ public OrSearchSpanDefinition(IEnumerable> claus _clauses = Ensure.IsNotNull(clauses, nameof(clauses)).ToList(); } - private protected override BsonDocument RenderClause(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => - new("clauses", new BsonArray(_clauses.Select(clause => clause.Render(documentSerializer, serializerRegistry)))); + private protected override BsonDocument RenderClause(SearchDefinitionRenderContext renderContext) => + new("clauses", new BsonArray(_clauses.Select(clause => clause.Render(renderContext)))); } internal sealed class SubtractSearchSpanDefinition : SearchSpanDefinition @@ -169,11 +169,11 @@ public SubtractSearchSpanDefinition(SearchSpanDefinition include, Sea _exclude = Ensure.IsNotNull(exclude, nameof(exclude)); } - private protected override BsonDocument RenderClause(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + private protected override BsonDocument RenderClause(SearchDefinitionRenderContext renderContext) => new() { - { "include", _include.Render(documentSerializer, serializerRegistry) }, - { "exclude", _exclude.Render(documentSerializer, serializerRegistry) }, + { "include", _include.Render(renderContext) }, + { "exclude", _exclude.Render(renderContext) }, }; } @@ -189,11 +189,11 @@ public TermSearchSpanDefinition(SearchPathDefinition path, SearchQuer _path = Ensure.IsNotNull(path, nameof(path)); } - private protected override BsonDocument RenderClause(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) => + private protected override BsonDocument RenderClause(SearchDefinitionRenderContext renderContext) => new() { { "query", _query.Render() }, - { "path", _path.Render(documentSerializer, serializerRegistry) }, + { "path", _path.Render(renderContext) }, }; } } diff --git a/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs b/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs index edf2a812ac4..dfa1fc93a01 100644 --- a/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs @@ -112,6 +112,26 @@ public void Count_total() results.Should().ContainSingle().Which.MetaResult.Count.Total.Should().Be(108); } + [Fact] + public void EmbeddedDocument() + { + var builderHistoricalDocument = Builders.Search; + var builderComments = Builders.Search; + + var result = GetTestCollection< HistoricalDocumentWithCommentsOnly>() + .Aggregate() + .Search(builderHistoricalDocument.EmbeddedDocument( + p => p.Comments, + builderComments.Text(p => p.Author, "Corliss Zuk"))) + .Limit(10) + .ToList(); + + foreach (var document in result) + { + document.Comments.Should().Contain(c => c.Author == "Corliss Zuk"); + } + } + [Fact] public void Exists() { @@ -487,10 +507,31 @@ private IMongoCollection GetTestCollection() => _disposableM .GetDatabase("sample_training") .GetCollection("posts"); + private IMongoCollection GetTestCollection() => _disposableMongoClient + .GetDatabase("sample_training") + .GetCollection("posts"); + private IMongoCollection GetGeoTestCollection() => _disposableMongoClient .GetDatabase("sample_airbnb") .GetCollection("listingsAndReviews"); + [BsonIgnoreExtraElements] + public class Comment + { + [BsonElement("author")] + public string Author { get; set; } + } + + [BsonIgnoreExtraElements] + public class HistoricalDocumentWithCommentsOnly + { + [BsonId] + public ObjectId Id { get; set; } + + [BsonElement("comments")] + public Comment[] Comments { get; set; } + } + [BsonIgnoreExtraElements] public class HistoricalDocument { diff --git a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs index 180e50b1aeb..7d81fa4fdfc 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs @@ -187,6 +187,81 @@ public void Compound_typed() "{ compound: { must: [{ exists: { path: 'age' } } ], score: { constant: { value: 123 } } } }"); } + [Fact] + public void EmbeddedDocument() + { + var subject = CreateSubject(); + + // Compound + var compound = subject + .Compound() + .Must(subject.Text("y", "1")) + .Should(subject.Exists("z")); + + AssertRendered( + subject.EmbeddedDocument("x", compound), + "{ embeddedDocument: { path : 'x', operator : { compound : { must : [{ text : { query : '1', path : 'x.y' } }], should : [{ exists : { path : 'x.z' } }] } } } }"); + + var scoreBuilder = new SearchScoreDefinitionBuilder(); + AssertRendered( + subject.EmbeddedDocument("x", compound, scoreBuilder.Constant(123)), + "{ embeddedDocument: { path : 'x', operator : { compound : { must : [{ text : { query : '1', path : 'x.y' } }], should : [{ exists : { path : 'x.z' } }] } }, score: { constant: { value: 123 } } } }"); + + // Multipath + AssertRendered( + subject.EmbeddedDocument("x", subject.Text(new[] { "y", "z", "w" }, "berg")), + "{ embeddedDocument: { path : 'x', operator : { text : { query : 'berg', path : ['x.y', 'x.z', 'x.w'] } } } }"); + + // Nested + AssertRendered( + subject.EmbeddedDocument("x", subject.EmbeddedDocument("y", subject.QueryString("z", "berg"))), + "{ embeddedDocument: { path : 'x', operator : { embeddedDocument: { path : 'x.y', operator : { 'queryString' : { defaultPath : 'x.y.z', query : 'berg' } } } } } }"); + + // Query + AssertRendered( + subject.EmbeddedDocument("x", subject.QueryString("y", "berg")), + "{ embeddedDocument: { path : 'x', operator : { 'queryString' : { defaultPath : 'x.y', query : 'berg' } } } }"); + } + + [Fact] + public void EmbeddedDocument_typed() + { + var subjectFamily = CreateSubject(); + var subjectPerson = CreateSubject(); + + // Compound + var compound = subjectPerson + .Compound() + .Must(subjectPerson.Text(p => p.FirstName, "John")) + .Should(subjectPerson.Text(p => p.LastName, "Smith")); + + AssertRendered( + subjectFamily.EmbeddedDocument(p => p.Children, compound), + "{ embeddedDocument: { path : 'Children', operator : { compound : { must : [{ text : { query : 'John', path : 'Children.fn' } }], should : [{ text : { query : 'Smith', path : 'Children.ln' } }] } } } }"); + + var scoreBuilder = new SearchScoreDefinitionBuilder(); + AssertRendered( + subjectFamily.EmbeddedDocument(p => p.Children, compound, scoreBuilder.Constant(123)), + "{ embeddedDocument: { path : 'Children', operator : { compound : { must : [{ text : { query : 'John', path : 'Children.fn' } }], should : [{ text : { query : 'Smith', path : 'Children.ln' } }] } }, score: { constant: { value: 123 } } } }"); + + // Nested + AssertRendered( + subjectFamily.EmbeddedDocument(p => p.Relatives, subjectFamily.EmbeddedDocument(p => p.Children, subjectPerson.Text(p => p.FirstName, "Alice"))), + "{ embeddedDocument: { path : 'Relatives', operator : { embeddedDocument: { path : 'Relatives.Children', operator : { 'text' : { path: 'Relatives.Children.fn', query : 'Alice' } } } } } }"); + + // Multipath + var pathBuilder = new SearchPathDefinitionBuilder(); + var multiPath = pathBuilder.Multi(p => p.FirstName, p => p.LastName); + AssertRendered( + subjectFamily.EmbeddedDocument(p => p.Children, subjectPerson.Text(multiPath, "berg")), + "{ embeddedDocument: { path : 'Children', operator : { text : { query : 'berg', path : ['Children.fn', 'Children.ln'] } } } }"); + + // Query + AssertRendered( + subjectFamily.EmbeddedDocument(p => p.Children, subjectPerson.QueryString(p => p.LastName, "berg")), + "{ embeddedDocument: { path : 'Children', operator : { 'queryString' : { defaultPath : 'Children.ln', query : 'berg' } } } }"); + } + [Theory] [MemberData(nameof(EqualsSupportedTypesTestData))] public void Equals_should_render_supported_type( @@ -1011,7 +1086,7 @@ private void AssertRendered(SearchDefinition query, string private void AssertRendered(SearchDefinition query, BsonDocument expected) { var documentSerializer = BsonSerializer.SerializerRegistry.GetSerializer(); - var renderedQuery = query.Render(documentSerializer, BsonSerializer.SerializerRegistry); + var renderedQuery = query.Render(new(documentSerializer, BsonSerializer.SerializerRegistry)); renderedQuery.Should().BeEquivalentTo(expected); } @@ -1052,6 +1127,16 @@ public class Person : SimplePerson public bool Retired { get; set; } } + public class Family + { + public SimplePerson Parent1 { get; set; } + public SimplePerson Parent2 { get; set; } + + public SimplePerson[] Children { get; set; } + + public Family[] Relatives { get; set; } + } + public class SimplePerson { [BsonElement("fn")] diff --git a/tests/MongoDB.Driver.Tests/Search/SearchFacetBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchFacetBuilderTests.cs index a65b39697e6..b5271fd2c49 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchFacetBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchFacetBuilderTests.cs @@ -118,7 +118,7 @@ private void AssertRendered(SearchFacet facet, string expe private void AssertRendered(SearchFacet facet, BsonDocument expected) { var documentSerializer = BsonSerializer.SerializerRegistry.GetSerializer(); - var renderedFacet = facet.Render(documentSerializer, BsonSerializer.SerializerRegistry); + var renderedFacet = facet.Render(new(documentSerializer, BsonSerializer.SerializerRegistry)); renderedFacet.Should().BeEquivalentTo(expected); } diff --git a/tests/MongoDB.Driver.Tests/Search/SearchPathDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchPathDefinitionBuilderTests.cs index c88c8db8fda..a3bb98eda7c 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchPathDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchPathDefinitionBuilderTests.cs @@ -33,6 +33,10 @@ public void Analyzer() AssertRendered( subject.Analyzer("x", "english"), "{ value: 'x', multi: 'english' }"); + AssertRendered( + subject.Analyzer("x", "english"), + "{ value: 'basepath.x', multi: 'english' }", + "basepath"); } [Fact] @@ -43,9 +47,17 @@ public void Analyzer_typed() AssertRendered( subject.Analyzer(x => x.FirstName, "english"), "{ value: 'fn', multi: 'english' }"); + AssertRendered( + subject.Analyzer(x => x.FirstName, "english"), + "{ value: 'basepath.fn', multi: 'english' }", + "basepath"); AssertRendered( subject.Analyzer("FirstName", "english"), "{ value: 'fn', multi: 'english' }"); + AssertRendered( + subject.Analyzer("FirstName", "english"), + "{ value: 'basepath.fn', multi: 'english' }", + "basepath"); } [Fact] @@ -60,6 +72,14 @@ public void Multi() new BsonString("x"), new BsonString("y") }); + AssertRendered( + subject.Multi("x", "y"), + new BsonArray() + { + new BsonString("basepath.x"), + new BsonString("basepath.y") + }, + "basepath"); AssertRendered( subject.Multi( new List>() @@ -72,6 +92,19 @@ public void Multi() new BsonString("x"), new BsonString("y") }); + AssertRendered( + subject.Multi( + new List>() + { + "x", + "y" + }), + new BsonArray() + { + new BsonString("basepath.x"), + new BsonString("basepath.y") + }, + "basepath"); } [Fact] @@ -86,6 +119,14 @@ public void Multi_typed() new BsonString("fn"), new BsonString("ln") }); + AssertRendered( + subject.Multi(x => x.FirstName, x => x.LastName), + new BsonArray() + { + new BsonString("basepath.fn"), + new BsonString("basepath.ln") + }, + "basepath"); AssertRendered( subject.Multi("FirstName", "LastName"), new BsonArray() @@ -93,6 +134,22 @@ public void Multi_typed() new BsonString("fn"), new BsonString("ln") }); + AssertRendered( + subject.Multi("FirstName", "LastName"), + new BsonArray() + { + new BsonString("basepath.fn"), + new BsonString("basepath.ln") + }, + "basepath"); + } + + [Fact] + public void NestedPath() + { + var subject = CreateSubject(); + AssertRendered(subject.Single("x"), new BsonString("a.b.x"), "a.b"); + AssertRendered(subject.Single("x"), new BsonString("a.b.c.x"), "a.b.c"); } [Fact] @@ -101,6 +158,7 @@ public void Single() var subject = CreateSubject(); AssertRendered(subject.Single("x"), new BsonString("x")); + AssertRendered(subject.Single("x"), new BsonString("basepath.x"), "basepath"); } [Fact] @@ -109,7 +167,9 @@ public void Single_typed() var subject = CreateSubject(); AssertRendered(subject.Single(x => x.FirstName), new BsonString("fn")); + AssertRendered(subject.Single(x => x.FirstName), new BsonString("basepath.fn"), "basepath"); AssertRendered(subject.Single("FirstName"), new BsonString("fn")); + AssertRendered(subject.Single("FirstName"), new BsonString("basepath.fn"), "basepath"); } [Fact] @@ -120,15 +180,19 @@ public void Wildcard() AssertRendered( subject.Wildcard("*"), "{ wildcard: '*' }"); + AssertRendered( + subject.Wildcard("*"), + "{ wildcard: '*' }", + "basepath"); } - private void AssertRendered(SearchPathDefinition path, string expected) => - AssertRendered(path, BsonDocument.Parse(expected)); + private void AssertRendered(SearchPathDefinition path, string expected, string pathPrefix = null) => + AssertRendered(path, BsonDocument.Parse(expected), pathPrefix); - private void AssertRendered(SearchPathDefinition path, BsonValue expected) + private void AssertRendered(SearchPathDefinition path, BsonValue expected, string pathPrefix = null) { var documentSerializer = BsonSerializer.SerializerRegistry.GetSerializer(); - var renderedPath = path.Render(documentSerializer, BsonSerializer.SerializerRegistry); + var renderedPath = path.Render(new(documentSerializer, BsonSerializer.SerializerRegistry, pathPrefix)); renderedPath.Should().Be(expected); } diff --git a/tests/MongoDB.Driver.Tests/Search/SearchScoreDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchScoreDefinitionBuilderTests.cs index 7c18f598bb6..9ca10967a4f 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchScoreDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchScoreDefinitionBuilderTests.cs @@ -86,13 +86,12 @@ private void AssertRendered(SearchScoreDefinition score, s private void AssertRendered(SearchScoreDefinition score, BsonDocument expected) { var documentSerializer = BsonSerializer.SerializerRegistry.GetSerializer(); - var renderedQuery = score.Render(documentSerializer, BsonSerializer.SerializerRegistry); + var renderedQuery = score.Render(new(documentSerializer, BsonSerializer.SerializerRegistry)); renderedQuery.Should().BeEquivalentTo(expected); } - private SearchScoreDefinitionBuilder CreateSubject() => - new SearchScoreDefinitionBuilder(); + private SearchScoreDefinitionBuilder CreateSubject() => new(); private class Person { diff --git a/tests/MongoDB.Driver.Tests/Search/SearchSpanDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchSpanDefinitionBuilderTests.cs index 2a020e5bae7..1b675b0fa19 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchSpanDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchSpanDefinitionBuilderTests.cs @@ -156,7 +156,7 @@ private void AssertRendered(SearchSpanDefinition span, str private void AssertRendered(SearchSpanDefinition span, BsonDocument expected) { var documentSerializer = BsonSerializer.SerializerRegistry.GetSerializer(); - var renderedSpan = span.Render(documentSerializer, BsonSerializer.SerializerRegistry); + var renderedSpan = span.Render(new(documentSerializer, BsonSerializer.SerializerRegistry)); renderedSpan.Should().BeEquivalentTo(expected); }