From b777fae2b291cf84c63c3286f900788c99d64142 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 31 Dec 2024 10:42:58 +0100 Subject: [PATCH 01/42] Initial --- .../Serializers/GuidSerializer.cs | 2 +- src/MongoDB.Driver/IAggregateFluent.cs | 1 - .../Search/AtlasSearchTests.cs | 35 +++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/MongoDB.Bson/Serialization/Serializers/GuidSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/GuidSerializer.cs index ccb0b8ee2c8..90fdd2c829f 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/GuidSerializer.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/GuidSerializer.cs @@ -172,7 +172,7 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati /// /// The GuidRepresentation. /// The reconfigured serializer. - public GuidSerializer WithGuidRepresentation(GuidRepresentation guidRepresentation) + public GuidSerializer WithGuidRepresentation(GuidRepresentation guidRepresentation) //TODO This method and the following one should use the current values of both guidRepresentation and representation, otherwise they are lost { return new GuidSerializer(guidRepresentation); } diff --git a/src/MongoDB.Driver/IAggregateFluent.cs b/src/MongoDB.Driver/IAggregateFluent.cs index dfb0b821ec8..e8d825c66e6 100644 --- a/src/MongoDB.Driver/IAggregateFluent.cs +++ b/src/MongoDB.Driver/IAggregateFluent.cs @@ -404,7 +404,6 @@ IAggregateFluent Lookup SetWindowFields( AggregateExpressionDefinition, TWindowFields> output); - //TODO If I add a parameter here, then this would be a binary breaking change /// /// Appends a $search stage to the pipeline. /// diff --git a/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs b/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs index d1b1a1a3e44..83f0649124b 100644 --- a/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs @@ -18,7 +18,9 @@ using System.Linq; using FluentAssertions; using MongoDB.Bson; +using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson.Serialization.Serializers; using MongoDB.Driver.Core.Misc; using MongoDB.Driver.Core.TestHelpers.Logging; using MongoDB.Driver.GeoJsonObjectModel; @@ -34,6 +36,8 @@ namespace MongoDB.Driver.Tests.Search [Trait("Category", "AtlasSearch")] public class AtlasSearchTests : LoggableTestClass { + private readonly ITestOutputHelper _testOutputHelper; + #region static private static readonly GeoJsonPolygon __testPolygon = @@ -58,6 +62,7 @@ public class AtlasSearchTests : LoggableTestClass public AtlasSearchTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { + _testOutputHelper = testOutputHelper; RequireEnvironment.Check().EnvironmentVariable("ATLAS_SEARCH_TESTS_ENABLED"); var atlasSearchUri = Environment.GetEnvironmentVariable("ATLAS_SEARCH"); @@ -735,6 +740,32 @@ private HistoricalDocument SearchSingle( return fluent.Limit(1).Single(); } + [Fact] + public void TestX() + { + BsonSerializer.RegisterSerializer(new GuidSerializer(BsonType.String)); + var testGuid = Guid.NewGuid(); + var searchDefinition = Builders.Search.Equals(t => t.TestGuid2, testGuid); + + var result = GetExtraTestsCollection() + .Aggregate() + .Search(searchDefinition); + _testOutputHelper.WriteLine(result.ToString()); + //result.Should().Be(" "); + + var matchFilter1 = Builders.Filter.Eq(f => f.TestGuid2, testGuid); + var matchFilter2 = Builders.Filter.Eq(f => f.TestGuid, testGuid); + + var result2 = GetExtraTestsCollection().Aggregate().Match(matchFilter1).Match(matchFilter2); + _testOutputHelper.WriteLine(result2.ToString()); + } + + /* Notes: + * - The match aggregation stage respects the correct serializer + * + * + */ + private List SearchMultipleSynonymMapping(params SearchDefinition[] clauses) => GetSynonymTestCollection().Aggregate() .Search(Builders.Search.Compound().Should(clauses), indexName: "synonyms-tests") @@ -878,6 +909,10 @@ private class TestClass [BsonGuidRepresentation(GuidRepresentation.Standard)] [BsonElement("testGuid")] public Guid TestGuid { get; set; } + + [BsonRepresentation(BsonType.String)] + [BsonElement("testGuid2")] + public Guid TestGuid2 { get; set; } } } } From 86ee6cf54163065d27e0dca9e659eee49f4686eb Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 31 Dec 2024 15:55:53 +0100 Subject: [PATCH 02/42] Various corrections --- .../Search/OperatorSearchDefinitions.cs | 44 +++++------ src/MongoDB.Driver/Search/SearchDefinition.cs | 4 +- .../Jira/CSharp5442Tests.cs | 77 +++++++++++++++++++ .../Search/AtlasSearchTests.cs | 35 --------- 4 files changed, 99 insertions(+), 61 deletions(-) create mode 100644 tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index 753d8c452b6..6ea7a7a3cb2 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -17,6 +17,7 @@ using System.Collections.Generic; using System.Linq; using MongoDB.Bson; +using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; using MongoDB.Driver.Core.Misc; using MongoDB.Driver.GeoJsonObjectModel; @@ -118,38 +119,33 @@ private protected override BsonDocument RenderArguments(RenderArgs ar internal sealed class EqualsSearchDefinition : OperatorSearchDefinition { - private readonly BsonValue _value; + private readonly TField _value; + private readonly FieldDefinition _field; public EqualsSearchDefinition(FieldDefinition path, TField value, SearchScoreDefinition score) : base(OperatorType.Equals, path, score) { - _value = ToBsonValue(value); + _value = value; + _field = path; } - private protected override BsonDocument RenderArguments(RenderArgs args) => - new("value", _value); + private protected override BsonDocument RenderArguments(RenderArgs args) + { + var fieldRenderArgs = args; + var renderedField = _field.Render(fieldRenderArgs); - private static BsonValue ToBsonValue(TField value) => - value switch + var document = new BsonDocument(); + using (var bsonWriter = new BsonDocumentWriter(document)) { - bool v => (BsonBoolean)v, - sbyte v => (BsonInt32)v, - byte v => (BsonInt32)v, - short v => (BsonInt32)v, - ushort v => (BsonInt32)v, - int v => (BsonInt32)v, - uint v => (BsonInt64)v, - long v => (BsonInt64)v, - float v => (BsonDouble)v, - double v => (BsonDouble)v, - DateTime v => (BsonDateTime)v, - DateTimeOffset v => (BsonDateTime)v.UtcDateTime, - ObjectId v => (BsonObjectId)v, - Guid v => new BsonBinaryData(v, GuidRepresentation.Standard), - string v => (BsonString)v, - null => BsonNull.Value, - _ => throw new InvalidCastException() - }; + var context = BsonSerializationContext.CreateRoot(bsonWriter); + bsonWriter.WriteStartDocument(); + bsonWriter.WriteName("value"); + renderedField.FieldSerializer.Serialize(context, _value); + bsonWriter.WriteEndDocument(); + } + + return document; + } } internal sealed class ExistsSearchDefinition : OperatorSearchDefinition diff --git a/src/MongoDB.Driver/Search/SearchDefinition.cs b/src/MongoDB.Driver/Search/SearchDefinition.cs index 3b4d23f720c..321c8b05fa4 100644 --- a/src/MongoDB.Driver/Search/SearchDefinition.cs +++ b/src/MongoDB.Driver/Search/SearchDefinition.cs @@ -123,9 +123,9 @@ private protected enum OperatorType QueryString, Range, Regex, - Search, + Search, //TODO This is never used Span, - Term, + Term, //TODO This is never used Text, Wildcard } diff --git a/tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs b/tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs new file mode 100644 index 00000000000..18d8fede5be --- /dev/null +++ b/tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs @@ -0,0 +1,77 @@ +/* Copyright 2010-present MongoDB Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using FluentAssertions; +using MongoDB.Bson; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson.Serialization.Serializers; +using Moq; +using Xunit; + +namespace MongoDB.Driver.Tests.Jira +{ + public class CSharp5442Tests + { + [Fact] + public void Search_Operators_use_correct_serializers_when_using_attributes() + { + var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); + var collection = new Mock>().Object; + var searchDefinition = Builders + .Search + .Compound() + .Must(Builders.Search.Equals(t => t.DefaultGuid, testGuid)) + .Must(Builders.Search.Equals(t => t.StringGuid, testGuid)); + + var result = collection.Aggregate().Search(searchDefinition).ToString(); + + const string expected = """aggregate([{ "$search" : { "compound" : { "must" : [{ "equals" : { "value" : { "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }, "path" : "DefaultGuid" } }, { "equals" : { "value" : "01020304-0506-0708-090a-0b0c0d0e0f10", "path" : "StringGuid" } }] } } }])"""; + + result.Should().Be(expected); + } + + [Fact] + public void Search_Operators_use_correct_serializers_when_using_serializer_registry() + { + BsonSerializer.RegisterSerializer(new GuidSerializer(BsonType.String)); + + var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); + var collection = new Mock>().Object; + var searchDefinition = Builders + .Search + .Compound() + .Must(Builders.Search.Equals(t => t.DefaultGuid, testGuid)); + + var result = collection.Aggregate().Search(searchDefinition).ToString(); + + const string expected = """aggregate([{ "$search" : { "compound" : { "must" : [{ "equals" : { "value" : { "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }, "path" : "DefaultGuid" } }] } } }])"""; + + result.Should().Be(expected); + } + + public class TestClass + { + [BsonGuidRepresentation(GuidRepresentation.Standard)] + public Guid DefaultGuid { get; set; } + + [BsonRepresentation(BsonType.String)] + public Guid StringGuid { get; set; } + } + + + } +} \ No newline at end of file diff --git a/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs b/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs index 83f0649124b..d1b1a1a3e44 100644 --- a/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/AtlasSearchTests.cs @@ -18,9 +18,7 @@ using System.Linq; using FluentAssertions; using MongoDB.Bson; -using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Attributes; -using MongoDB.Bson.Serialization.Serializers; using MongoDB.Driver.Core.Misc; using MongoDB.Driver.Core.TestHelpers.Logging; using MongoDB.Driver.GeoJsonObjectModel; @@ -36,8 +34,6 @@ namespace MongoDB.Driver.Tests.Search [Trait("Category", "AtlasSearch")] public class AtlasSearchTests : LoggableTestClass { - private readonly ITestOutputHelper _testOutputHelper; - #region static private static readonly GeoJsonPolygon __testPolygon = @@ -62,7 +58,6 @@ public class AtlasSearchTests : LoggableTestClass public AtlasSearchTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { - _testOutputHelper = testOutputHelper; RequireEnvironment.Check().EnvironmentVariable("ATLAS_SEARCH_TESTS_ENABLED"); var atlasSearchUri = Environment.GetEnvironmentVariable("ATLAS_SEARCH"); @@ -740,32 +735,6 @@ private HistoricalDocument SearchSingle( return fluent.Limit(1).Single(); } - [Fact] - public void TestX() - { - BsonSerializer.RegisterSerializer(new GuidSerializer(BsonType.String)); - var testGuid = Guid.NewGuid(); - var searchDefinition = Builders.Search.Equals(t => t.TestGuid2, testGuid); - - var result = GetExtraTestsCollection() - .Aggregate() - .Search(searchDefinition); - _testOutputHelper.WriteLine(result.ToString()); - //result.Should().Be(" "); - - var matchFilter1 = Builders.Filter.Eq(f => f.TestGuid2, testGuid); - var matchFilter2 = Builders.Filter.Eq(f => f.TestGuid, testGuid); - - var result2 = GetExtraTestsCollection().Aggregate().Match(matchFilter1).Match(matchFilter2); - _testOutputHelper.WriteLine(result2.ToString()); - } - - /* Notes: - * - The match aggregation stage respects the correct serializer - * - * - */ - private List SearchMultipleSynonymMapping(params SearchDefinition[] clauses) => GetSynonymTestCollection().Aggregate() .Search(Builders.Search.Compound().Should(clauses), indexName: "synonyms-tests") @@ -909,10 +878,6 @@ private class TestClass [BsonGuidRepresentation(GuidRepresentation.Standard)] [BsonElement("testGuid")] public Guid TestGuid { get; set; } - - [BsonRepresentation(BsonType.String)] - [BsonElement("testGuid2")] - public Guid TestGuid2 { get; set; } } } } From 9c7c4a459fce45f0682e4c119269885ad7203ed7 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 31 Dec 2024 16:12:01 +0100 Subject: [PATCH 03/42] Corrections --- .../Serialization/Serializers/GuidSerializer.cs | 2 +- tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/MongoDB.Bson/Serialization/Serializers/GuidSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/GuidSerializer.cs index 90fdd2c829f..ccb0b8ee2c8 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/GuidSerializer.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/GuidSerializer.cs @@ -172,7 +172,7 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati /// /// The GuidRepresentation. /// The reconfigured serializer. - public GuidSerializer WithGuidRepresentation(GuidRepresentation guidRepresentation) //TODO This method and the following one should use the current values of both guidRepresentation and representation, otherwise they are lost + public GuidSerializer WithGuidRepresentation(GuidRepresentation guidRepresentation) { return new GuidSerializer(guidRepresentation); } diff --git a/tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs b/tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs index 18d8fede5be..f69ad6cd105 100644 --- a/tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs +++ b/tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs @@ -44,21 +44,19 @@ public void Search_Operators_use_correct_serializers_when_using_attributes() result.Should().Be(expected); } - [Fact] + [Fact(Skip = "This should only be run manually due to the use of BsonSerializer.RegisterSerializer")] public void Search_Operators_use_correct_serializers_when_using_serializer_registry() { BsonSerializer.RegisterSerializer(new GuidSerializer(BsonType.String)); - var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); var collection = new Mock>().Object; var searchDefinition = Builders .Search - .Compound() - .Must(Builders.Search.Equals(t => t.DefaultGuid, testGuid)); + .Equals(t => t.UndefinedRepresentationGuid, testGuid); var result = collection.Aggregate().Search(searchDefinition).ToString(); - const string expected = """aggregate([{ "$search" : { "compound" : { "must" : [{ "equals" : { "value" : { "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }, "path" : "DefaultGuid" } }] } } }])"""; + const string expected = """aggregate([{ "$search" : { "equals" : { "value" : "01020304-0506-0708-090a-0b0c0d0e0f10", "path" : "UndefinedRepresentationGuid" } } }])"""; result.Should().Be(expected); } @@ -70,6 +68,8 @@ public class TestClass [BsonRepresentation(BsonType.String)] public Guid StringGuid { get; set; } + + public Guid UndefinedRepresentationGuid { get; set; } } From b616cbc033d86a4605fb0835cf3efccbf8c13bfa Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 31 Dec 2024 16:13:47 +0100 Subject: [PATCH 04/42] Small fix --- .../Search/OperatorSearchDefinitions.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index 6ea7a7a3cb2..29376f5645d 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -131,18 +131,15 @@ public EqualsSearchDefinition(FieldDefinition path, TField value, Sea private protected override BsonDocument RenderArguments(RenderArgs args) { - var fieldRenderArgs = args; - var renderedField = _field.Render(fieldRenderArgs); + var renderedField = _field.Render(args); var document = new BsonDocument(); - using (var bsonWriter = new BsonDocumentWriter(document)) - { - var context = BsonSerializationContext.CreateRoot(bsonWriter); - bsonWriter.WriteStartDocument(); - bsonWriter.WriteName("value"); - renderedField.FieldSerializer.Serialize(context, _value); - bsonWriter.WriteEndDocument(); - } + using var bsonWriter = new BsonDocumentWriter(document); + var context = BsonSerializationContext.CreateRoot(bsonWriter); + bsonWriter.WriteStartDocument(); + bsonWriter.WriteName("value"); + renderedField.FieldSerializer.Serialize(context, _value); + bsonWriter.WriteEndDocument(); return document; } From d7db9a0b2c50229742d4b0ecdc726297c51f2c6e Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 31 Dec 2024 16:28:41 +0100 Subject: [PATCH 05/42] Removed extra spaces --- tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs b/tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs index f69ad6cd105..fec9322d6f9 100644 --- a/tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs +++ b/tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs @@ -71,7 +71,5 @@ public class TestClass public Guid UndefinedRepresentationGuid { get; set; } } - - } } \ No newline at end of file From c7d2881bb5190ea079f01af388e5bbb01b780ed9 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 31 Dec 2024 18:33:55 +0100 Subject: [PATCH 06/42] Small corrections --- .../Search/OperatorSearchDefinitions.cs | 43 +++++++------------ .../Search/SearchDefinitionBuilderTests.cs | 2 +- 2 files changed, 16 insertions(+), 29 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index 29376f5645d..420d54c8e4d 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -236,7 +236,7 @@ public InSearchDefinition( private protected override BsonDocument RenderArguments(RenderArgs args) => new("value", _values); - private static BsonValue ToBsonValue(TField value) => + private static BsonValue ToBsonValue(TField value) => //TODO probably we need to change also this value switch { bool v => (BsonBoolean)v, @@ -351,43 +351,31 @@ private protected override BsonDocument RenderArguments(RenderArgs ar } internal sealed class RangeSearchDefinition : OperatorSearchDefinition + where TField : struct, IComparable { - private readonly SearchRangeV2 _range; - + private readonly SearchRange _range; + private readonly BsonValue _min; + private readonly BsonValue _max; + public RangeSearchDefinition( SearchPathDefinition path, - SearchRangeV2 range, + SearchRange range, SearchScoreDefinition score) : base(OperatorType.Range, path, score) { _range = range; + _min = ToBsonValue(_range.Min); + _max = ToBsonValue(_range.Max); } - private protected override BsonDocument RenderArguments(RenderArgs args) - { - BsonValue min = null, max = null; - bool minInclusive = false, maxInclusive = false; - - if (_range.Min != null) - { - min = ToBsonValue(_range.Min.Value); - minInclusive = _range.Min.Inclusive; - } - - if (_range.Max != null) - { - max = ToBsonValue(_range.Max.Value); - maxInclusive = _range.Max.Inclusive; - } - - return new() + private protected override BsonDocument RenderArguments(RenderArgs args) => + new() { - { minInclusive ? "gte" : "gt", min, min != null }, - { maxInclusive ? "lte" : "lt", max, max != null } + { _range.IsMinInclusive ? "gte" : "gt", _min, _min != null }, + { _range.IsMaxInclusive ? "lte" : "lt", _max, _max != null }, }; - } - - private static BsonValue ToBsonValue(TField value) => + + private static BsonValue ToBsonValue(TField? value) => //TODO Probably we need to change this too value switch { sbyte v => (BsonInt32)v, @@ -401,7 +389,6 @@ private static BsonValue ToBsonValue(TField value) => double v => (BsonDouble)v, DateTime v => (BsonDateTime)v, DateTimeOffset v => (BsonDateTime)v.UtcDateTime, - string v => (BsonString)v, null => null, _ => throw new InvalidCastException() }; diff --git a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs index 05bcbaf1829..07eb8dccc0c 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs @@ -276,7 +276,7 @@ public void Equals_with_array_should_render_supported_type() } [Theory] - [MemberData(nameof(EqualsSupportedTypesTestData))] + [MemberData(nameof(EqualsSupportedTypesTestData))] //TODO Need to fix this public void Equals_should_render_supported_type( T value, string valueRendered, From c19972d8eaf2d1a19ebd5f49aedd56ef1abd05d5 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Thu, 2 Jan 2025 11:13:02 +0100 Subject: [PATCH 07/42] Added tests and fixed --- .../Search/OperatorSearchDefinitions.cs | 6 ++--- .../Jira/CSharp5442Tests.cs | 25 ++++++++++++++++--- .../Search/SearchDefinitionBuilderTests.cs | 4 ++- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index 420d54c8e4d..f06c6b1ebb2 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -120,13 +120,13 @@ private protected override BsonDocument RenderArguments(RenderArgs ar internal sealed class EqualsSearchDefinition : OperatorSearchDefinition { private readonly TField _value; - private readonly FieldDefinition _field; + private readonly FieldDefinition _field; - public EqualsSearchDefinition(FieldDefinition path, TField value, SearchScoreDefinition score) + public EqualsSearchDefinition(FieldDefinition path, FieldDefinition field, TField value, SearchScoreDefinition score) : base(OperatorType.Equals, path, score) { _value = value; - _field = path; + _field = field; } private protected override BsonDocument RenderArguments(RenderArgs args) diff --git a/tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs b/tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs index fec9322d6f9..a1afee72c12 100644 --- a/tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs +++ b/tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs @@ -27,7 +27,7 @@ namespace MongoDB.Driver.Tests.Jira public class CSharp5442Tests { [Fact] - public void Search_Operators_use_correct_serializers_when_using_attributes() + public void Search_Operators_use_correct_serializers_when_using_attributes_and_expression_path() { var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); var collection = new Mock>().Object; @@ -44,7 +44,26 @@ public void Search_Operators_use_correct_serializers_when_using_attributes() result.Should().Be(expected); } - [Fact(Skip = "This should only be run manually due to the use of BsonSerializer.RegisterSerializer")] + [Fact] + public void Search_Operators_use_correct_serializers_when_using_attributes_and_string_path() + { + var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); + var collection = new Mock>().Object; + var searchDefinition = Builders + .Search + .Compound() + .Must(Builders.Search.Equals("DefaultGuid", testGuid)) + .Must(Builders.Search.Equals("StringGuid", testGuid)); + + var result = collection.Aggregate().Search(searchDefinition).ToString(); + + const string expected = """aggregate([{ "$search" : { "compound" : { "must" : [{ "equals" : { "value" : { "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }, "path" : "DefaultGuid" } }, { "equals" : { "value" : "01020304-0506-0708-090a-0b0c0d0e0f10", "path" : "StringGuid" } }] } } }])"""; + + result.Should().Be(expected); + } + + //[Fact(Skip = "This should only be run manually due to the use of BsonSerializer.RegisterSerializer")] //TODO Put back skip afterwards + [Fact] public void Search_Operators_use_correct_serializers_when_using_serializer_registry() { BsonSerializer.RegisterSerializer(new GuidSerializer(BsonType.String)); @@ -61,7 +80,7 @@ public void Search_Operators_use_correct_serializers_when_using_serializer_regis result.Should().Be(expected); } - public class TestClass + private class TestClass { [BsonGuidRepresentation(GuidRepresentation.Standard)] public Guid DefaultGuid { get; set; } diff --git a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs index 07eb8dccc0c..f29903ed034 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs @@ -298,6 +298,8 @@ public void Equals_should_render_supported_type( AssertRendered( subjectTyped.Equals(fieldExpression, value), $"{{ equals: {{ path: '{fieldRendered}', value: {valueRendered} }} }}"); + + //For the Guid we get: GuidSerializer cannot serialize a Guid when GuidRepresentation is Unspecified. It makes sense } public static object[][] EqualsSupportedTypesTestData => new[] @@ -313,7 +315,7 @@ public void Equals_should_render_supported_type( new object[] { (float)1, "1", Exp(p => p.Float), nameof(Person.Float) }, new object[] { (double)1, "1", Exp(p => p.Double), nameof(Person.Double) }, new object[] { DateTime.MinValue, "ISODate(\"0001-01-01T00:00:00Z\")", Exp(p => p.Birthday), "dob" }, - new object[] { DateTimeOffset.MaxValue, "ISODate(\"9999-12-31T23:59:59.999Z\")", Exp(p => p.DateTimeOffset), nameof(Person.DateTimeOffset) }, + new object[] { DateTimeOffset.MaxValue, """{ "DateTime" : { "$date" : "9999-12-31T23:59:59.999Z" }, "Ticks" : 3155378975999999999, "Offset" : 0 }""", Exp(p => p.DateTimeOffset), nameof(Person.DateTimeOffset) }, new object[] { ObjectId.Empty, "{ $oid: '000000000000000000000000' }", Exp(p => p.Id), "_id" }, new object[] { Guid.Empty, """{ "$binary" : { "base64" : "AAAAAAAAAAAAAAAAAAAAAA==", "subType" : "04" } }""", Exp(p => p.Guid), nameof(Person.Guid) }, new object[] { null, "null", Exp(p => p.Name), nameof(Person.Name) }, From 250e5be598d85bfa8b527ec7a48bdc2786418331 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Thu, 2 Jan 2025 11:14:03 +0100 Subject: [PATCH 08/42] Missing statment --- src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index f06c6b1ebb2..0e21dbe1eab 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -138,7 +138,7 @@ private protected override BsonDocument RenderArguments(RenderArgs ar var context = BsonSerializationContext.CreateRoot(bsonWriter); bsonWriter.WriteStartDocument(); bsonWriter.WriteName("value"); - renderedField.FieldSerializer.Serialize(context, _value); + renderedField.ValueSerializer.Serialize(context, _value); bsonWriter.WriteEndDocument(); return document; From d210cd8ee3f6c56167524d8a5de58497c5a34e6b Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Thu, 2 Jan 2025 12:00:20 +0100 Subject: [PATCH 09/42] Various corrections --- .../Search/OperatorSearchDefinitions.cs | 51 ++++++++++++++----- .../Search/SearchDefinitionBuilderTests.cs | 4 ++ 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index 0e21dbe1eab..e17c0f24f6b 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -121,27 +121,54 @@ internal sealed class EqualsSearchDefinition : OperatorSearch { private readonly TField _value; private readonly FieldDefinition _field; + private readonly FieldDefinition> _arrayField; - public EqualsSearchDefinition(FieldDefinition path, FieldDefinition field, TField value, SearchScoreDefinition score) - : base(OperatorType.Equals, path, score) + public EqualsSearchDefinition(FieldDefinition path, TField value, SearchScoreDefinition score) + : base(OperatorType.Equals, new SingleSearchPathDefinition(path), score) { _value = value; - _field = field; + _field = path; + } + + public EqualsSearchDefinition(FieldDefinition> path, TField value, SearchScoreDefinition score) + : base(OperatorType.Equals, new SingleSearchPathDefinition(path), score) + { + _value = value; + _arrayField = path; } private protected override BsonDocument RenderArguments(RenderArgs args) { - var renderedField = _field.Render(args); + if (_field is null) + { + var fieldRenderArgs = args with { PathRenderArgs = args.PathRenderArgs with { AllowScalarValueForArray = true } }; + + var renderedField = _arrayField.Render(fieldRenderArgs); - var document = new BsonDocument(); - using var bsonWriter = new BsonDocumentWriter(document); - var context = BsonSerializationContext.CreateRoot(bsonWriter); - bsonWriter.WriteStartDocument(); - bsonWriter.WriteName("value"); - renderedField.ValueSerializer.Serialize(context, _value); - bsonWriter.WriteEndDocument(); + var document = new BsonDocument(); + using var bsonWriter = new BsonDocumentWriter(document); + var context = BsonSerializationContext.CreateRoot(bsonWriter); + bsonWriter.WriteStartDocument(); + bsonWriter.WriteName("value"); + renderedField.ValueSerializer.Serialize(context, _value); + bsonWriter.WriteEndDocument(); - return document; + return document; + } + else + { + var renderedField = _field.Render(args); + + var document = new BsonDocument(); + using var bsonWriter = new BsonDocumentWriter(document); + var context = BsonSerializationContext.CreateRoot(bsonWriter); + bsonWriter.WriteStartDocument(); + bsonWriter.WriteName("value"); + renderedField.ValueSerializer.Serialize(context, _value); + bsonWriter.WriteEndDocument(); + + return document; + } } } diff --git a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs index f29903ed034..a1229b7ebb9 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs @@ -273,6 +273,10 @@ public void Equals_with_array_should_render_supported_type() AssertRendered( subjectTyped.Equals(p => p.Hobbies, "soccer"), "{ equals: { path: 'hobbies', value: 'soccer' } }"); + + AssertRendered( + subjectTyped.Equals("x", "soccer"), + "{ equals: { path: 'x', value: 'soccer' } }"); } [Theory] From a9f63a813c264ebc823e32d1292f400f6779c772 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Thu, 2 Jan 2025 12:19:06 +0100 Subject: [PATCH 10/42] Corrections --- src/MongoDB.Driver/FieldValueSerializerHelper.cs | 2 ++ src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs | 10 +++++++--- .../Search/SearchDefinitionBuilderTests.cs | 4 ---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/MongoDB.Driver/FieldValueSerializerHelper.cs b/src/MongoDB.Driver/FieldValueSerializerHelper.cs index 16d95460897..3f2a884b378 100644 --- a/src/MongoDB.Driver/FieldValueSerializerHelper.cs +++ b/src/MongoDB.Driver/FieldValueSerializerHelper.cs @@ -317,6 +317,8 @@ internal class IEnumerableSerializer : SerializerBase> { private readonly IBsonSerializer _itemSerializer; + public IBsonSerializer ItemSerializer => _itemSerializer; + public IEnumerableSerializer(IBsonSerializer itemSerializer) { _itemSerializer = itemSerializer; diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index e17c0f24f6b..db9ff8bc9cf 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -141,16 +141,20 @@ private protected override BsonDocument RenderArguments(RenderArgs ar { if (_field is null) { - var fieldRenderArgs = args with { PathRenderArgs = args.PathRenderArgs with { AllowScalarValueForArray = true } }; + //var fieldRenderArgs = args with { PathRenderArgs = args.PathRenderArgs with { AllowScalarValueForArray = true } }; - var renderedField = _arrayField.Render(fieldRenderArgs); + var renderedField = _arrayField.Render(args); + + var serializer = + (renderedField.ValueSerializer as FieldValueSerializerHelper.IEnumerableSerializer) + .ItemSerializer; var document = new BsonDocument(); using var bsonWriter = new BsonDocumentWriter(document); var context = BsonSerializationContext.CreateRoot(bsonWriter); bsonWriter.WriteStartDocument(); bsonWriter.WriteName("value"); - renderedField.ValueSerializer.Serialize(context, _value); + serializer.Serialize(context, _value); bsonWriter.WriteEndDocument(); return document; diff --git a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs index a1229b7ebb9..f29903ed034 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs @@ -273,10 +273,6 @@ public void Equals_with_array_should_render_supported_type() AssertRendered( subjectTyped.Equals(p => p.Hobbies, "soccer"), "{ equals: { path: 'hobbies', value: 'soccer' } }"); - - AssertRendered( - subjectTyped.Equals("x", "soccer"), - "{ equals: { path: 'x', value: 'soccer' } }"); } [Theory] From ed6afcdc5daa710b97b93b05832671e6258eb40f Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Thu, 2 Jan 2025 13:02:37 +0100 Subject: [PATCH 11/42] Small fix --- tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs b/tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs index a1afee72c12..69666bc88ac 100644 --- a/tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs +++ b/tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs @@ -62,8 +62,7 @@ public void Search_Operators_use_correct_serializers_when_using_attributes_and_s result.Should().Be(expected); } - //[Fact(Skip = "This should only be run manually due to the use of BsonSerializer.RegisterSerializer")] //TODO Put back skip afterwards - [Fact] + [Fact(Skip = "This should only be run manually due to the use of BsonSerializer.RegisterSerializer")] //TODO Put back skip afterwards public void Search_Operators_use_correct_serializers_when_using_serializer_registry() { BsonSerializer.RegisterSerializer(new GuidSerializer(BsonType.String)); @@ -80,7 +79,7 @@ public void Search_Operators_use_correct_serializers_when_using_serializer_regis result.Should().Be(expected); } - private class TestClass + public class TestClass { [BsonGuidRepresentation(GuidRepresentation.Standard)] public Guid DefaultGuid { get; set; } From b189ee428d0725e708ac9ae12c11717e9c758b15 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Thu, 2 Jan 2025 14:18:41 +0100 Subject: [PATCH 12/42] Small correction --- .../Search/OperatorSearchDefinitions.cs | 31 +++++++++++++++++-- .../Search/SearchDefinitionBuilderTests.cs | 4 +-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index db9ff8bc9cf..49b1a3ab8f4 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -123,9 +123,29 @@ internal sealed class EqualsSearchDefinition : OperatorSearch private readonly FieldDefinition _field; private readonly FieldDefinition> _arrayField; + private readonly List _validTypes = + [ + typeof(bool), + typeof(sbyte), + typeof(byte), + typeof(short), + typeof(ushort), + typeof(int), + typeof(uint), + typeof(long), + typeof(float), + typeof(double), + typeof(DateTime), + typeof(DateTimeOffset), + typeof(ObjectId), + typeof(Guid), + typeof(string) + ]; + public EqualsSearchDefinition(FieldDefinition path, TField value, SearchScoreDefinition score) : base(OperatorType.Equals, new SingleSearchPathDefinition(path), score) { + ValidateType(); _value = value; _field = path; } @@ -133,6 +153,7 @@ public EqualsSearchDefinition(FieldDefinition path, TField va public EqualsSearchDefinition(FieldDefinition> path, TField value, SearchScoreDefinition score) : base(OperatorType.Equals, new SingleSearchPathDefinition(path), score) { + ValidateType(); _value = value; _arrayField = path; } @@ -141,8 +162,6 @@ private protected override BsonDocument RenderArguments(RenderArgs ar { if (_field is null) { - //var fieldRenderArgs = args with { PathRenderArgs = args.PathRenderArgs with { AllowScalarValueForArray = true } }; - var renderedField = _arrayField.Render(args); var serializer = @@ -174,6 +193,14 @@ private protected override BsonDocument RenderArguments(RenderArgs ar return document; } } + + private void ValidateType() + { + if (!_validTypes.Contains(typeof(TField))) + { + throw new InvalidCastException(); + } + } } internal sealed class ExistsSearchDefinition : OperatorSearchDefinition diff --git a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs index f29903ed034..2cf030d4764 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs @@ -290,6 +290,8 @@ public void Equals_should_render_supported_type( subject.Equals("x", value), $"{{ equals: {{ path: 'x', value: {valueRendered} }} }}"); + //For the Guid we get (correctly): GuidSerializer cannot serialize a Guid when GuidRepresentation is Unspecified. + var scoreBuilder = new SearchScoreDefinitionBuilder(); AssertRendered( subject.Equals("x", value, scoreBuilder.Constant(1)), @@ -298,8 +300,6 @@ public void Equals_should_render_supported_type( AssertRendered( subjectTyped.Equals(fieldExpression, value), $"{{ equals: {{ path: '{fieldRendered}', value: {valueRendered} }} }}"); - - //For the Guid we get: GuidSerializer cannot serialize a Guid when GuidRepresentation is Unspecified. It makes sense } public static object[][] EqualsSupportedTypesTestData => new[] From 8fb8979214dd377f9418bd563c9fa4906290f4fb Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Mon, 6 Jan 2025 10:45:15 +0100 Subject: [PATCH 13/42] Moved testing to main file --- .../Jira/CSharp5442Tests.cs | 93 ------------------- .../Search/SearchDefinitionBuilderTests.cs | 61 +++++++++++- 2 files changed, 59 insertions(+), 95 deletions(-) delete mode 100644 tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs diff --git a/tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs b/tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs deleted file mode 100644 index 69666bc88ac..00000000000 --- a/tests/MongoDB.Driver.Tests/Jira/CSharp5442Tests.cs +++ /dev/null @@ -1,93 +0,0 @@ -/* Copyright 2010-present MongoDB Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -using System; -using FluentAssertions; -using MongoDB.Bson; -using MongoDB.Bson.Serialization; -using MongoDB.Bson.Serialization.Attributes; -using MongoDB.Bson.Serialization.Serializers; -using Moq; -using Xunit; - -namespace MongoDB.Driver.Tests.Jira -{ - public class CSharp5442Tests - { - [Fact] - public void Search_Operators_use_correct_serializers_when_using_attributes_and_expression_path() - { - var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); - var collection = new Mock>().Object; - var searchDefinition = Builders - .Search - .Compound() - .Must(Builders.Search.Equals(t => t.DefaultGuid, testGuid)) - .Must(Builders.Search.Equals(t => t.StringGuid, testGuid)); - - var result = collection.Aggregate().Search(searchDefinition).ToString(); - - const string expected = """aggregate([{ "$search" : { "compound" : { "must" : [{ "equals" : { "value" : { "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }, "path" : "DefaultGuid" } }, { "equals" : { "value" : "01020304-0506-0708-090a-0b0c0d0e0f10", "path" : "StringGuid" } }] } } }])"""; - - result.Should().Be(expected); - } - - [Fact] - public void Search_Operators_use_correct_serializers_when_using_attributes_and_string_path() - { - var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); - var collection = new Mock>().Object; - var searchDefinition = Builders - .Search - .Compound() - .Must(Builders.Search.Equals("DefaultGuid", testGuid)) - .Must(Builders.Search.Equals("StringGuid", testGuid)); - - var result = collection.Aggregate().Search(searchDefinition).ToString(); - - const string expected = """aggregate([{ "$search" : { "compound" : { "must" : [{ "equals" : { "value" : { "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }, "path" : "DefaultGuid" } }, { "equals" : { "value" : "01020304-0506-0708-090a-0b0c0d0e0f10", "path" : "StringGuid" } }] } } }])"""; - - result.Should().Be(expected); - } - - [Fact(Skip = "This should only be run manually due to the use of BsonSerializer.RegisterSerializer")] //TODO Put back skip afterwards - public void Search_Operators_use_correct_serializers_when_using_serializer_registry() - { - BsonSerializer.RegisterSerializer(new GuidSerializer(BsonType.String)); - var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); - var collection = new Mock>().Object; - var searchDefinition = Builders - .Search - .Equals(t => t.UndefinedRepresentationGuid, testGuid); - - var result = collection.Aggregate().Search(searchDefinition).ToString(); - - const string expected = """aggregate([{ "$search" : { "equals" : { "value" : "01020304-0506-0708-090a-0b0c0d0e0f10", "path" : "UndefinedRepresentationGuid" } } }])"""; - - result.Should().Be(expected); - } - - public class TestClass - { - [BsonGuidRepresentation(GuidRepresentation.Standard)] - public Guid DefaultGuid { get; set; } - - [BsonRepresentation(BsonType.String)] - public Guid StringGuid { get; set; } - - public Guid UndefinedRepresentationGuid { get; set; } - } - } -} \ No newline at end of file diff --git a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs index 2cf030d4764..20a69d35bda 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs @@ -20,6 +20,7 @@ using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson.Serialization.Serializers; using MongoDB.Driver.GeoJsonObjectModel; using MongoDB.Driver.Search; using Xunit; @@ -249,7 +250,8 @@ public void EmbeddedDocument_typed() // Nested AssertRendered( - subjectFamily.EmbeddedDocument(p => p.Relatives, subjectFamily.EmbeddedDocument(p => p.Children, subjectPerson.Text(p => p.FirstName, "Alice"))), + 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 @@ -290,7 +292,7 @@ public void Equals_should_render_supported_type( subject.Equals("x", value), $"{{ equals: {{ path: 'x', value: {valueRendered} }} }}"); - //For the Guid we get (correctly): GuidSerializer cannot serialize a Guid when GuidRepresentation is Unspecified. + //TODO For the Guid we get (correctly): GuidSerializer cannot serialize a Guid when GuidRepresentation is Unspecified. var scoreBuilder = new SearchScoreDefinitionBuilder(); AssertRendered( @@ -322,6 +324,49 @@ public void Equals_should_render_supported_type( new object[] { "Jim", "\"Jim\"", Exp(p => p.FirstName), "fn" } }; + [Fact] + public void Equals_should_use_correct_serializers_when_using_attributes_and_expression_path() + { + var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); + var subjectTyped = CreateSubject(); + + AssertRendered( + subjectTyped.Equals(t => t.DefaultGuid, testGuid), + """{ "equals" : { "value" : { "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }, "path" : "DefaultGuid" } } """); + + AssertRendered( + subjectTyped.Equals(t => t.StringGuid, testGuid), + """{ "equals" : { "value" : "01020304-0506-0708-090a-0b0c0d0e0f10", "path" : "StringGuid" } }"""); + } + + [Fact] + public void Equals_should_use_correct_serializers_when_using_attributes_and_string_path() + { + var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); + var subjectTyped = CreateSubject(); + + AssertRendered( + subjectTyped.Equals("DefaultGuid", testGuid), + """{ "equals" : { "value" : { "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }, "path" : "DefaultGuid" } } """); + + AssertRendered( + subjectTyped.Equals("StringGuid", testGuid), + """{ "equals" : { "value" : "01020304-0506-0708-090a-0b0c0d0e0f10", "path" : "StringGuid" } }"""); + } + + [Fact(Skip = "This should only be run manually due to the use of BsonSerializer.RegisterSerializer")] + public void Equals_should_use_correct_serializers_when_using_serializer_registry() + { + BsonSerializer.RegisterSerializer(new GuidSerializer(BsonType.String)); + + var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); + var subjectTyped = CreateSubject(); + + AssertRendered( + subjectTyped.Equals(t => t.UndefinedRepresentationGuid, testGuid), + """{ "equals" : { "value" : "01020304-0506-0708-090a-0b0c0d0e0f10", "path" : "UndefinedRepresentationGuid" } }"""); + } + [Theory] [MemberData(nameof(EqualsUnsupportedTypesTestData))] public void Equals_should_throw_on_unsupported_type(T value, Expression> fieldExpression) @@ -1259,6 +1304,8 @@ public class Person : SimplePerson public float Float { get; set; } public double Double { get; set; } public decimal Decimal { get; set; } + + [BsonGuidRepresentation(GuidRepresentation.Standard)] public Guid Guid { get; set; } public DateTimeOffset DateTimeOffset { get; set; } public TimeSpan TimeSpan { get; set; } @@ -1312,6 +1359,16 @@ public class SimplestPerson { [BsonElement("fn")] public string FirstName { get; set; } + + public class TestGuidClass + { + [BsonGuidRepresentation(GuidRepresentation.Standard)] + public Guid DefaultGuid { get; set; } + + [BsonRepresentation(BsonType.String)] + public Guid StringGuid { get; set; } + + public Guid UndefinedRepresentationGuid { get; set; } } } } From de751e44d6d7d6af7e6c3f762ad1a91b8e1910fc Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Mon, 6 Jan 2025 10:45:36 +0100 Subject: [PATCH 14/42] Corrected operator --- .../FieldValueSerializerHelper.cs | 11 ++- .../Search/OperatorSearchDefinitions.cs | 68 ++++--------------- 2 files changed, 23 insertions(+), 56 deletions(-) diff --git a/src/MongoDB.Driver/FieldValueSerializerHelper.cs b/src/MongoDB.Driver/FieldValueSerializerHelper.cs index 3f2a884b378..13f34747052 100644 --- a/src/MongoDB.Driver/FieldValueSerializerHelper.cs +++ b/src/MongoDB.Driver/FieldValueSerializerHelper.cs @@ -141,6 +141,7 @@ public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSer return ConvertIfPossibleSerializer.Create(valueType, fieldType, fieldSerializer, serializerRegistry); } + //TODO Never used public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSerializer, IBsonSerializerRegistry serializerRegistry, Type valueType, object value) { if (!valueType.GetTypeInfo().IsValueType && value == null) @@ -313,17 +314,21 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati } } - internal class IEnumerableSerializer : SerializerBase> + internal class IEnumerableSerializer : SerializerBase>, IBsonArraySerializer { private readonly IBsonSerializer _itemSerializer; - public IBsonSerializer ItemSerializer => _itemSerializer; - public IEnumerableSerializer(IBsonSerializer itemSerializer) { _itemSerializer = itemSerializer; } + public bool TryGetItemSerializationInfo(out BsonSerializationInfo serializationInfo) + { + serializationInfo = new BsonSerializationInfo(null, _itemSerializer, typeof(TItem)); + return true; + } + public override bool Equals(object obj) { if (object.ReferenceEquals(obj, null)) { return false; } diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index 49b1a3ab8f4..68c215d692a 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -21,6 +21,7 @@ using MongoDB.Bson.Serialization; using MongoDB.Driver.Core.Misc; using MongoDB.Driver.GeoJsonObjectModel; +using MongoDB.Driver.Linq.Linq3Implementation.Misc; namespace MongoDB.Driver.Search { @@ -119,33 +120,13 @@ private protected override BsonDocument RenderArguments(RenderArgs ar internal sealed class EqualsSearchDefinition : OperatorSearchDefinition { - private readonly TField _value; + private readonly TField _value; //TODO Consider changing TFIeld to TValue (more correct) private readonly FieldDefinition _field; private readonly FieldDefinition> _arrayField; - private readonly List _validTypes = - [ - typeof(bool), - typeof(sbyte), - typeof(byte), - typeof(short), - typeof(ushort), - typeof(int), - typeof(uint), - typeof(long), - typeof(float), - typeof(double), - typeof(DateTime), - typeof(DateTimeOffset), - typeof(ObjectId), - typeof(Guid), - typeof(string) - ]; - public EqualsSearchDefinition(FieldDefinition path, TField value, SearchScoreDefinition score) : base(OperatorType.Equals, new SingleSearchPathDefinition(path), score) { - ValidateType(); _value = value; _field = path; } @@ -153,53 +134,34 @@ public EqualsSearchDefinition(FieldDefinition path, TField va public EqualsSearchDefinition(FieldDefinition> path, TField value, SearchScoreDefinition score) : base(OperatorType.Equals, new SingleSearchPathDefinition(path), score) { - ValidateType(); _value = value; _arrayField = path; } private protected override BsonDocument RenderArguments(RenderArgs args) { + IBsonSerializer valueSerializer; + if (_field is null) { var renderedField = _arrayField.Render(args); - - var serializer = - (renderedField.ValueSerializer as FieldValueSerializerHelper.IEnumerableSerializer) - .ItemSerializer; - - var document = new BsonDocument(); - using var bsonWriter = new BsonDocumentWriter(document); - var context = BsonSerializationContext.CreateRoot(bsonWriter); - bsonWriter.WriteStartDocument(); - bsonWriter.WriteName("value"); - serializer.Serialize(context, _value); - bsonWriter.WriteEndDocument(); - - return document; + valueSerializer = (IBsonSerializer)ArraySerializerHelper.GetItemSerializer(renderedField.ValueSerializer); } else { var renderedField = _field.Render(args); - - var document = new BsonDocument(); - using var bsonWriter = new BsonDocumentWriter(document); - var context = BsonSerializationContext.CreateRoot(bsonWriter); - bsonWriter.WriteStartDocument(); - bsonWriter.WriteName("value"); - renderedField.ValueSerializer.Serialize(context, _value); - bsonWriter.WriteEndDocument(); - - return document; + valueSerializer = renderedField.ValueSerializer; } - } - private void ValidateType() - { - if (!_validTypes.Contains(typeof(TField))) - { - throw new InvalidCastException(); - } + var document = new BsonDocument(); + using var bsonWriter = new BsonDocumentWriter(document); + var context = BsonSerializationContext.CreateRoot(bsonWriter); + bsonWriter.WriteStartDocument(); + bsonWriter.WriteName("value"); + valueSerializer.Serialize(context, _value); + bsonWriter.WriteEndDocument(); + + return document; } } From 73fbd04242b0868bf7f11635947e3fe8777bde1e Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Mon, 6 Jan 2025 10:46:50 +0100 Subject: [PATCH 15/42] Added missing parenthesis --- .../MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs index 20a69d35bda..ef1f7973f92 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs @@ -1359,6 +1359,7 @@ public class SimplestPerson { [BsonElement("fn")] public string FirstName { get; set; } + } public class TestGuidClass { From 5177c59cd1bb1602c735cb5d01fee8fade8a8a49 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Mon, 6 Jan 2025 12:08:42 +0100 Subject: [PATCH 16/42] Removed unsupported test --- .../Search/SearchDefinitionBuilderTests.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs index ef1f7973f92..4886464978b 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs @@ -367,17 +367,6 @@ public void Equals_should_use_correct_serializers_when_using_serializer_registry """{ "equals" : { "value" : "01020304-0506-0708-090a-0b0c0d0e0f10", "path" : "UndefinedRepresentationGuid" } }"""); } - [Theory] - [MemberData(nameof(EqualsUnsupportedTypesTestData))] - public void Equals_should_throw_on_unsupported_type(T value, Expression> fieldExpression) - { - var subject = CreateSubject(); - Record.Exception(() => subject.Equals("x", value)).Should().BeOfType(); - - var subjectTyped = CreateSubject(); - Record.Exception(() => subjectTyped.Equals(fieldExpression, value)).Should().BeOfType(); - } - public static object[][] EqualsUnsupportedTypesTestData => new[] { new object[] { (ulong)1, Exp(p => p.UInt64) }, From 8e53214542016d239ce056ab47b78687aac77cf4 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Mon, 6 Jan 2025 17:26:15 +0100 Subject: [PATCH 17/42] Added support for In --- .../Search/OperatorSearchDefinitions.cs | 57 +++++++------ src/MongoDB.Driver/Search/SearchDefinition.cs | 4 +- .../Search/SearchDefinitionBuilder.cs | 2 +- .../Search/SearchPathDefinitionBuilder.cs | 2 + .../Search/SearchDefinitionBuilderTests.cs | 83 ++++++++++++------- 5 files changed, 86 insertions(+), 62 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index 68c215d692a..fcc614f75c9 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -19,6 +19,7 @@ using MongoDB.Bson; using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Serializers; using MongoDB.Driver.Core.Misc; using MongoDB.Driver.GeoJsonObjectModel; using MongoDB.Driver.Linq.Linq3Implementation.Misc; @@ -238,7 +239,7 @@ private protected override BsonDocument RenderArguments(RenderArgs ar internal sealed class InSearchDefinition : OperatorSearchDefinition { - private readonly BsonArray _values; + private readonly TField[] _values; public InSearchDefinition( SearchPathDefinition path, @@ -247,36 +248,38 @@ public InSearchDefinition( : base(OperatorType.In, path, score) { Ensure.IsNotNullOrEmpty(values, nameof(values)); - var array = new BsonArray(values.Select(ToBsonValue)); - - var bsonType = array[0].GetType(); - _values = Ensure.That(array, arr => arr.All(v => v.GetType() == bsonType), nameof(values), "All values must be of the same type."); + _values = values.ToArray(); } - private protected override BsonDocument RenderArguments(RenderArgs args) => - new("value", _values); + private protected override BsonDocument RenderArguments(RenderArgs args) + { + IBsonSerializer serializer; - private static BsonValue ToBsonValue(TField value) => //TODO probably we need to change also this - value switch + if (_path is SingleSearchPathDefinition pd) { - bool v => (BsonBoolean)v, - sbyte v => (BsonInt32)v, - byte v => (BsonInt32)v, - short v => (BsonInt32)v, - ushort v => (BsonInt32)v, - int v => (BsonInt32)v, - uint v => (BsonInt64)v, - long v => (BsonInt64)v, - float v => (BsonDouble)v, - double v => (BsonDouble)v, - decimal v => (BsonDecimal128)v, - DateTime v => (BsonDateTime)v, - DateTimeOffset v => (BsonDateTime)v.UtcDateTime, - string v => (BsonString)v, - ObjectId v => (BsonObjectId)v, - Guid v => new BsonBinaryData(v, GuidRepresentation.Standard), - _ => throw new InvalidCastException() - }; + var renderedField = pd.Field.Render(args); + serializer = renderedField.FieldSerializer switch + { + null => new ArraySerializer(BsonSerializer.LookupSerializer()), + IBsonArraySerializer => renderedField.FieldSerializer, + _ => new ArraySerializer((IBsonSerializer)renderedField.FieldSerializer) + }; + } + else + { + serializer = new ArraySerializer(BsonSerializer.LookupSerializer()); + } + + var document = new BsonDocument(); + using var bsonWriter = new BsonDocumentWriter(document); + var context = BsonSerializationContext.CreateRoot(bsonWriter); + bsonWriter.WriteStartDocument(); + bsonWriter.WriteName("value"); + serializer.Serialize(context, _values); + bsonWriter.WriteEndDocument(); + + return document; + } } internal sealed class MoreLikeThisSearchDefinition : OperatorSearchDefinition diff --git a/src/MongoDB.Driver/Search/SearchDefinition.cs b/src/MongoDB.Driver/Search/SearchDefinition.cs index 321c8b05fa4..b5868a8d9ec 100644 --- a/src/MongoDB.Driver/Search/SearchDefinition.cs +++ b/src/MongoDB.Driver/Search/SearchDefinition.cs @@ -123,9 +123,9 @@ private protected enum OperatorType QueryString, Range, Regex, - Search, //TODO This is never used + Search, //TODO This is never used, should we remove them? Span, - Term, //TODO This is never used + Term, //TODO This is never used, should we remove them? Text, Wildcard } diff --git a/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs b/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs index 09c5172a7f8..4d96a0d40b4 100644 --- a/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs +++ b/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs @@ -119,7 +119,7 @@ public SearchDefinition EmbeddedDocument( /// The score modifier. /// An equality search definition. public SearchDefinition Equals( - FieldDefinition path, + FieldDefinition path, //TODO We should try to uniform this to the In operator for next major (this should be SearchPathDefinition) TField value, SearchScoreDefinition score = null) => new EqualsSearchDefinition(path, value, score); diff --git a/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs b/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs index 5c63b8430cc..6088ab13dce 100644 --- a/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs +++ b/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs @@ -137,6 +137,8 @@ internal sealed class SingleSearchPathDefinition : SearchPathDefiniti { private readonly FieldDefinition _field; + public FieldDefinition Field => _field; + public SingleSearchPathDefinition(FieldDefinition field) { _field = Ensure.IsNotNull(field, nameof(field)); diff --git a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs index 4886464978b..20c26a2dc95 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs @@ -367,12 +367,6 @@ public void Equals_should_use_correct_serializers_when_using_serializer_registry """{ "equals" : { "value" : "01020304-0506-0708-090a-0b0c0d0e0f10", "path" : "UndefinedRepresentationGuid" } }"""); } - public static object[][] EqualsUnsupportedTypesTestData => new[] - { - new object[] { (ulong)1, Exp(p => p.UInt64) }, - new object[] { TimeSpan.Zero, Exp(p => p.TimeSpan) }, - }; - [Fact] public void Exists() { @@ -581,7 +575,7 @@ public void In_typed( } public static readonly object[][] InTypedTestData = - { + { new object[] { new bool[] { true, false }, new[] { "true", "false" }, Exp(p => p.Retired), "ret" }, new object[] { new byte[] { 1, 2 }, new[] { "1", "2" }, Exp(p => p.UInt8), nameof(Person.UInt8) }, new object[] { new sbyte[] { 1, 2 }, new[] { "1", "2" }, Exp(p => p.Int8), nameof(Person.Int8) }, @@ -595,23 +589,12 @@ public void In_typed( new object[] { new decimal[] { 1.5m, 2.5m }, new[] { "NumberDecimal(\"1.5\")", "NumberDecimal(\"2.5\")" }, Exp(p => p.Decimal), nameof(Person.Decimal) }, new object[] { new[] { "str1", "str2" }, new[] { "'str1'", "'str2'" }, Exp(p => p.FirstName), "fn" }, new object[] { new[] { DateTime.MinValue, DateTime.MaxValue }, new[] { "ISODate(\"0001-01-01T00:00:00Z\")", "ISODate(\"9999-12-31T23:59:59.999Z\")" }, Exp(p => p.Birthday), "dob" }, - new object[] { new[] { DateTimeOffset.MinValue, DateTimeOffset.MaxValue }, new[] { "ISODate(\"0001-01-01T00:00:00Z\")", "ISODate(\"9999-12-31T23:59:59.999Z\")" }, Exp(p => p.DateTimeOffset), nameof(Person.DateTimeOffset)}, + new object[] { new[] { DateTimeOffset.MinValue, DateTimeOffset.MaxValue }, new[] { """{ "DateTime" : { "$date" : { "$numberLong" : "-62135596800000" } }, "Ticks" : 0, "Offset" : 0 } """, """ { "DateTime" : { "$date" : "9999-12-31T23:59:59.999Z" }, "Ticks" : 3155378975999999999, "Offset" : 0 } """ }, Exp(p => p.DateTimeOffset), nameof(Person.DateTimeOffset)}, new object[] { new[] { ObjectId.Empty, ObjectId.Parse("4d0ce088e447ad08b4721a37") }, new[] { "{ $oid: '000000000000000000000000' }", "{ $oid: '4d0ce088e447ad08b4721a37' }" }, Exp(p => p.Id), "_id" }, new object[] { new[] { Guid.Empty, Guid.Parse("b52af144-bc97-454f-a578-418a64fa95bf") }, new[] { """{ "$binary" : { "base64" : "AAAAAAAAAAAAAAAAAAAAAA==", "subType" : "04" } }""", """{ "$binary" : { "base64" : "tSrxRLyXRU+leEGKZPqVvw==", "subType" : "04" } }""" }, Exp(p => p.Guid), nameof(Person.Guid) }, - new object[] { new object[] { (byte)1, (short)2, (int)3 }, new[] { "1", "2", "3" }, Exp(p => p.Object), nameof(Person.Object) } + new object[] { new object[] { (byte)1, (short)2, (int)3 }, new[] { "1", "2", "3" }, Exp(p => p.Object), nameof(Person.Object) } //TODO How do we solve this? Should we actually support this? }; - [Theory] - [MemberData(nameof(InUnsupportedTypesTestData))] - public void In_should_throw_on_unsupported_types(T value, Expression> fieldExpression) - { - var subject = CreateSubject(); - Record.Exception(() => subject.In("x", new[] { value } )).Should().BeOfType(); - - var subjectTyped = CreateSubject(); - Record.Exception(() => subjectTyped.In(fieldExpression, new[] { value })).Should().BeOfType(); - } - [Fact] public void In_should_throw_when_values_are_invalid() { @@ -624,34 +607,70 @@ public void In_should_throw_when_values_are_invalid() Record.Exception(() => subjectTyped.In(p => p.Age, null)).Should().BeOfType(); } - public static object[][] InUnsupportedTypesTestData => new[] + [Fact] + public void In_should_use_correct_serializers_when_using_attributes_and_expression_path() { - new object[] { (ulong)1, Exp(p => p.UInt64) }, - new object[] { TimeSpan.Zero, Exp(p => p.TimeSpan) }, - }; + var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); + var subjectTyped = CreateSubject(); + + AssertRendered( + subjectTyped.In(t => t.DefaultGuid, [testGuid, testGuid]), + """{ "in" : { "value" : [{ "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }, { "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }], "path" : "DefaultGuid" } } """); + + AssertRendered( + subjectTyped.In(t => t.StringGuid, [testGuid, testGuid]), + """{ "in" : { "value" : ["01020304-0506-0708-090a-0b0c0d0e0f10", "01020304-0506-0708-090a-0b0c0d0e0f10"], "path" : "StringGuid" } }"""); + } [Fact] - public void In_should_throw_when_values_are_not_of_same_type() + public void In_should_use_correct_serializers_when_using_attributes_and_string_path() { - var values = new object[] { 1.5, 1 }; + var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); + var subjectTyped = CreateSubject(); - var subject = CreateSubject(); - Record.Exception(() => subject.In("x", values)).Should().BeOfType(); + AssertRendered( + subjectTyped.In("DefaultGuid", [testGuid, testGuid]), + """{ "in" : { "value" : [{ "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }, { "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }], "path" : "DefaultGuid" } } """); - var subjectTyped = CreateSubject(); - Record.Exception(() => subjectTyped.In(p => p.Object, values)).Should().BeOfType(); + AssertRendered( + subjectTyped.In("StringGuid", [testGuid, testGuid]), + """{ "in" : { "value" : ["01020304-0506-0708-090a-0b0c0d0e0f10", "01020304-0506-0708-090a-0b0c0d0e0f10"], "path" : "StringGuid" } }"""); } - + + [Fact(Skip = "This should only be run manually due to the use of BsonSerializer.RegisterSerializer")] + public void In_should_use_correct_serializers_when_using_serializer_registry() + { + BsonSerializer.RegisterSerializer(new GuidSerializer(BsonType.String)); + + var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); + var subjectTyped = CreateSubject(); + + AssertRendered( + subjectTyped.In(t => t.UndefinedRepresentationGuid, [testGuid, testGuid]), + """{ "in" : { "value" : ["01020304-0506-0708-090a-0b0c0d0e0f10", "01020304-0506-0708-090a-0b0c0d0e0f10"], "path" : "UndefinedRepresentationGuid" } }"""); + } + [Fact] public void In_with_array_field_should_render_correctly() { var subjectTyped = CreateSubject(); - + AssertRendered( subjectTyped.In(p => p.Hobbies, ["dance", "ski"]), "{ in: { path: 'hobbies', value: ['dance', 'ski'] } }"); } + [Fact] + public void In_with_wildcard_path_should_render_correctly() + { + var subjectTyped = CreateSubject(); + + var path = new SearchPathDefinitionBuilder(); + AssertRendered( + subjectTyped.In(path.Wildcard("*"), ["dance"]), + "{ in: { path: { wildcard: '*'}, value: ['dance'] } }"); + } + [Fact] public void MoreLikeThis() { From ca055843f991d2014043291450fd89f93fcad5c1 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Mon, 6 Jan 2025 18:22:50 +0100 Subject: [PATCH 18/42] Added support for range --- .../Search/OperatorSearchDefinitions.cs | 67 +++++++++------- .../Search/SearchDefinitionBuilderTests.cs | 77 +++++++++++-------- 2 files changed, 81 insertions(+), 63 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index fcc614f75c9..4ceb717b60c 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -141,12 +141,12 @@ public EqualsSearchDefinition(FieldDefinition> pa private protected override BsonDocument RenderArguments(RenderArgs args) { - IBsonSerializer valueSerializer; + IBsonSerializer valueSerializer; if (_field is null) { var renderedField = _arrayField.Render(args); - valueSerializer = (IBsonSerializer)ArraySerializerHelper.GetItemSerializer(renderedField.ValueSerializer); + valueSerializer = ArraySerializerHelper.GetItemSerializer(renderedField.ValueSerializer); } else { @@ -255,9 +255,9 @@ private protected override BsonDocument RenderArguments(RenderArgs ar { IBsonSerializer serializer; - if (_path is SingleSearchPathDefinition pd) + if (_path is SingleSearchPathDefinition searchPathDefinition) { - var renderedField = pd.Field.Render(args); + var renderedField = searchPathDefinition.Field.Render(args); serializer = renderedField.FieldSerializer switch { null => new ArraySerializer(BsonSerializer.LookupSerializer()), @@ -377,8 +377,6 @@ internal sealed class RangeSearchDefinition : OperatorSearchD where TField : struct, IComparable { private readonly SearchRange _range; - private readonly BsonValue _min; - private readonly BsonValue _max; public RangeSearchDefinition( SearchPathDefinition path, @@ -387,34 +385,45 @@ public RangeSearchDefinition( : base(OperatorType.Range, path, score) { _range = range; - _min = ToBsonValue(_range.Min); - _max = ToBsonValue(_range.Max); } - private protected override BsonDocument RenderArguments(RenderArgs args) => - new() + private protected override BsonDocument RenderArguments(RenderArgs args) + { + IBsonSerializer serializer; + + if (_path is SingleSearchPathDefinition searchPathDefinition) { - { _range.IsMinInclusive ? "gte" : "gt", _min, _min != null }, - { _range.IsMaxInclusive ? "lte" : "lt", _max, _max != null }, - }; + var renderedField = searchPathDefinition.Field.Render(args); + serializer = renderedField.FieldSerializer switch + { + null => BsonSerializer.LookupSerializer(), + IBsonArraySerializer => ArraySerializerHelper.GetItemSerializer(renderedField.FieldSerializer), + _ => renderedField.FieldSerializer + }; + } + else + { + serializer = BsonSerializer.LookupSerializer(); + } - private static BsonValue ToBsonValue(TField? value) => //TODO Probably we need to change this too - value switch + var document = new BsonDocument(); + using var bsonWriter = new BsonDocumentWriter(document); + var context = BsonSerializationContext.CreateRoot(bsonWriter); + bsonWriter.WriteStartDocument(); + if (_range.Min is not null) { - sbyte v => (BsonInt32)v, - byte v => (BsonInt32)v, - short v => (BsonInt32)v, - ushort v => (BsonInt32)v, - int v => (BsonInt32)v, - uint v => (BsonInt64)v, - long v => (BsonInt64)v, - float v => (BsonDouble)v, - double v => (BsonDouble)v, - DateTime v => (BsonDateTime)v, - DateTimeOffset v => (BsonDateTime)v.UtcDateTime, - null => null, - _ => throw new InvalidCastException() - }; + bsonWriter.WriteName(_range.IsMinInclusive? "gte" : "gt"); + serializer.Serialize(context, _range.Min); + } + if (_range.Max is not null) + { + bsonWriter.WriteName(_range.IsMaxInclusive? "lte" : "lt"); + serializer.Serialize(context, _range.Max); + } + bsonWriter.WriteEndDocument(); + + return document; + } } internal sealed class RegexSearchDefinition : OperatorSearchDefinition diff --git a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs index 20c26a2dc95..b4003fb4c19 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs @@ -911,6 +911,30 @@ public void QueryString_typed() "{ queryString: { defaultPath: 'hobbies', query: 'foo' } }"); } + [Fact] + public void RangeDateTime() + { + var subject = CreateSubject(); + + AssertRendered( + subject.Range( + p => p.Birthday, + SearchRangeBuilder + .Gte(new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc)) + .Lte(new DateTime(2009, 12, 31, 0, 0, 0, DateTimeKind.Utc))), + "{ range: { path: 'dob', gte: { $date: '2000-01-01T00:00:00Z' }, lte: { $date: '2009-12-31T00:00:00Z' } } }"); + } + + [Fact] + public void RangeDouble() + { + var subject = CreateSubject(); + + AssertRendered( + subject.Range(p => p.Age, SearchRangeBuilder.Gt(1.5).Lt(2.5)), + "{ range: { path: 'age', gt: 1.5, lt: 2.5 } }"); + } + [Theory] [InlineData(1, null, false, false, "gt: 1")] [InlineData(1, null, true, false, "gte: 1")] @@ -922,21 +946,23 @@ public void QueryString_typed() [InlineData(1, 10, true, true, "gte: 1, lte: 10")] public void Range_should_render_correct_operator(int? min, int? max, bool minInclusive, bool maxInclusive, string rangeRendered) { - var searchRange = new SearchRange(min, max, minInclusive, maxInclusive); - - var searchRangev2 = new SearchRangeV2( - min.HasValue ? new(min.Value, minInclusive) : null, - max.HasValue ? new(max.Value, maxInclusive) : null); - var subject = CreateSubject(); - - var searchRangeQuery = subject.Range("x", searchRange); - var searchRangeV2Query = subject.Range("x", searchRangev2); + AssertRendered( + subject.Range("x", new SearchRange(min, max, minInclusive, maxInclusive)), + $"{{ range: {{ path: 'x', {rangeRendered} }} }}"); + } - var expected = $"{{ range: {{ path: 'x', {rangeRendered} }} }}"; + [Fact] + public void RangeInt32_typed() + { + var subject = CreateSubject(); - AssertRendered(searchRangeQuery, expected); - AssertRendered(searchRangeV2Query, expected); + AssertRendered( + subject.Range(x => x.Age, SearchRangeBuilder.Gte(18).Lt(65)), + "{ range: { path: 'age', gte: 18, lt: 65 } }"); + AssertRendered( + subject.Range("Age", SearchRangeBuilder.Gte(18).Lt(65)), + "{ range: { path: 'age', gte: 18, lt: 65 } }"); } [Theory] @@ -948,16 +974,17 @@ public void Range_should_render_supported_types( string maxRendered, Expression> fieldExpression, string fieldRendered) + where T : struct, IComparable { var subject = CreateSubject(); var subjectTyped = CreateSubject(); AssertRendered( - subject.Range("testField", SearchRangeV2Builder.Gte(min).Lt(max)), - $"{{ range: {{ path: 'testField', gte: {minRendered}, lt: {maxRendered} }} }}"); + subject.Range("age", SearchRangeBuilder.Gte(min).Lt(max)), + $"{{ range: {{ path: 'age', gte: {minRendered}, lt: {maxRendered} }} }}"); AssertRendered( - subjectTyped.Range(fieldExpression, SearchRangeV2Builder.Gte(min).Lt(max)), + subjectTyped.Range(fieldExpression, SearchRangeBuilder.Gte(min).Lt(max)), $"{{ range: {{ path: '{fieldRendered}', gte: {minRendered}, lt: {maxRendered} }} }}"); } @@ -972,27 +999,9 @@ public void Range_should_render_supported_types( new object[] { long.MinValue, long.MaxValue, "NumberLong(\"-9223372036854775808\")", "NumberLong(\"9223372036854775807\")", Exp(p => p.Int64), nameof(Person.Int64) }, new object[] { (float)1, (float)2, "1", "2", Exp(p => p.Float), nameof(Person.Float) }, new object[] { (double)1, (double)2, "1", "2", Exp(p => p.Double), nameof(Person.Double) }, - new object[] { "A", "D", "'A'", "'D'", Exp(p => p.FirstName), "fn" }, new object[] { DateTime.MinValue, DateTime.MaxValue, "ISODate(\"0001-01-01T00:00:00Z\")", "ISODate(\"9999-12-31T23:59:59.999Z\")", Exp(p => p.Birthday), "dob" }, new object[] { DateTimeOffset.MinValue, DateTimeOffset.MaxValue, "ISODate(\"0001-01-01T00:00:00Z\")", "ISODate(\"9999-12-31T23:59:59.999Z\")", Exp(p => p.DateTimeOffset), nameof(Person.DateTimeOffset) } }; - - [Theory] - [MemberData(nameof(RangeUnsupportedTypesTestData))] - public void Range_should_throw_on_unsupported_types(T value, Expression> fieldExpression) - { - var subject = CreateSubject(); - Record.Exception(() => subject.Range("age", SearchRangeV2Builder.Gte(value).Lt(value)).Render(new RenderArgs())).Should().BeOfType(); - - var subjectTyped = CreateSubject(); - Record.Exception(() => subjectTyped.Range(fieldExpression, SearchRangeV2Builder.Gte(value).Lt(value)).Render(new RenderArgs())).Should().BeOfType(); - } - - public static object[][] RangeUnsupportedTypesTestData => new[] - { - new object[] { (ulong)1, Exp(p => p.UInt64) }, - new object[] { TimeSpan.Zero, Exp(p => p.TimeSpan) }, - }; [Fact] public void Range_with_array_field_should_render_correctly() @@ -1000,7 +1009,7 @@ public void Range_with_array_field_should_render_correctly() var subject = CreateSubject(); AssertRendered( - subject.Range(x => x.SalaryHistory, SearchRangeV2Builder.Gte(1000).Lt(2000)), + subject.Range(x => x.SalaryHistory, SearchRangeBuilder.Gte(1000).Lt(2000)), "{ range: { path: 'salaries', gte: 1000, lt: 2000 } }"); } From f402b0e3f4318c291b930d2ff867f002ec144520 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 7 Jan 2025 10:57:22 +0100 Subject: [PATCH 19/42] Corrected tests --- .../Search/OperatorSearchDefinitions.cs | 1 + .../Search/SearchDefinitionBuilderTests.cs | 102 +++++++++++++----- 2 files changed, 79 insertions(+), 24 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index 4ceb717b60c..d2f0751037b 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -125,6 +125,7 @@ internal sealed class EqualsSearchDefinition : OperatorSearch private readonly FieldDefinition _field; private readonly FieldDefinition> _arrayField; + //TODO I think that if we use the search definition here we can use only one constructor and uniform it to the other two public EqualsSearchDefinition(FieldDefinition path, TField value, SearchScoreDefinition score) : base(OperatorType.Equals, new SingleSearchPathDefinition(path), score) { diff --git a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs index b4003fb4c19..5a97cb3bb55 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs @@ -288,16 +288,18 @@ public void Equals_should_render_supported_type( var subject = CreateSubject(); var subjectTyped = CreateSubject(); - AssertRendered( - subject.Equals("x", value), - $"{{ equals: {{ path: 'x', value: {valueRendered} }} }}"); + //There is no property called "x" where to pick up a properly configured GuidSerializer for the tests + if (typeof(T) != typeof(Guid)) + { + AssertRendered( + subject.Equals("x", value), + $"{{ equals: {{ path: 'x', value: {valueRendered} }} }}"); - //TODO For the Guid we get (correctly): GuidSerializer cannot serialize a Guid when GuidRepresentation is Unspecified. - - var scoreBuilder = new SearchScoreDefinitionBuilder(); - AssertRendered( - subject.Equals("x", value, scoreBuilder.Constant(1)), - $"{{ equals: {{ path: 'x', value: {valueRendered}, score: {{ constant: {{ value: 1 }} }} }} }}"); + var scoreBuilder = new SearchScoreDefinitionBuilder(); + AssertRendered( + subject.Equals("x", value, scoreBuilder.Constant(1)), + $"{{ equals: {{ path: 'x', value: {valueRendered}, score: {{ constant: {{ value: 1 }} }} }} }}"); + } AssertRendered( subjectTyped.Equals(fieldExpression, value), @@ -328,7 +330,7 @@ public void Equals_should_render_supported_type( public void Equals_should_use_correct_serializers_when_using_attributes_and_expression_path() { var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); - var subjectTyped = CreateSubject(); + var subjectTyped = CreateSubject(); AssertRendered( subjectTyped.Equals(t => t.DefaultGuid, testGuid), @@ -343,7 +345,7 @@ public void Equals_should_use_correct_serializers_when_using_attributes_and_expr public void Equals_should_use_correct_serializers_when_using_attributes_and_string_path() { var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); - var subjectTyped = CreateSubject(); + var subjectTyped = CreateSubject(); AssertRendered( subjectTyped.Equals("DefaultGuid", testGuid), @@ -360,7 +362,7 @@ public void Equals_should_use_correct_serializers_when_using_serializer_registry BsonSerializer.RegisterSerializer(new GuidSerializer(BsonType.String)); var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); - var subjectTyped = CreateSubject(); + var subjectTyped = CreateSubject(); AssertRendered( subjectTyped.Equals(t => t.UndefinedRepresentationGuid, testGuid), @@ -549,9 +551,8 @@ public void In(T[] fieldValues, string[] fieldsRendered) new object[] { new decimal[] { 1.5m, 2.5m }, new[] { "NumberDecimal(\"1.5\")", "NumberDecimal(\"2.5\")" } }, new object[] { new[] { "str1", "str2" }, new[] { "'str1'", "'str2'" } }, new object[] { new[] { DateTime.MinValue, DateTime.MaxValue }, new[] { "ISODate(\"0001-01-01T00:00:00Z\")", "ISODate(\"9999-12-31T23:59:59.999Z\")" } }, - new object[] { new[] { DateTimeOffset.MinValue, DateTimeOffset.MaxValue }, new[] { "ISODate(\"0001-01-01T00:00:00Z\")", "ISODate(\"9999-12-31T23:59:59.999Z\")" } }, + new object[] { new[] { DateTimeOffset.MinValue, DateTimeOffset.MaxValue }, new[] { """{ "DateTime" : { "$date" : { "$numberLong" : "-62135596800000" } }, "Ticks" : 0, "Offset" : 0 } """, """ { "DateTime" : { "$date" : "9999-12-31T23:59:59.999Z" }, "Ticks" : 3155378975999999999, "Offset" : 0 } """ } }, new object[] { new[] { ObjectId.Empty, ObjectId.Parse("4d0ce088e447ad08b4721a37") }, new[] { "{ $oid: '000000000000000000000000' }", "{ $oid: '4d0ce088e447ad08b4721a37' }" } }, - new object[] { new object[] { (byte)1, (short)2, (int)3 }, new[] { "1", "2", "3" } } }; [Theory] @@ -565,9 +566,13 @@ public void In_typed( var subject = CreateSubject(); var fieldValuesArray = $"[{string.Join(",", fieldValuesRendered)}]"; - AssertRendered( - subject.In("x", fieldValues), - $"{{ in: {{ path: 'x', value: {fieldValuesArray} }} }}"); + //There is no property called "x" where to pick up a properly configured GuidSerializer for the tests + if (typeof(T) != typeof(Guid)) + { + AssertRendered( + subject.In("x", fieldValues), + $"{{ in: {{ path: 'x', value: {fieldValuesArray} }} }}"); + } AssertRendered( subject.In(fieldExpression, fieldValues), @@ -592,7 +597,6 @@ public void In_typed( new object[] { new[] { DateTimeOffset.MinValue, DateTimeOffset.MaxValue }, new[] { """{ "DateTime" : { "$date" : { "$numberLong" : "-62135596800000" } }, "Ticks" : 0, "Offset" : 0 } """, """ { "DateTime" : { "$date" : "9999-12-31T23:59:59.999Z" }, "Ticks" : 3155378975999999999, "Offset" : 0 } """ }, Exp(p => p.DateTimeOffset), nameof(Person.DateTimeOffset)}, new object[] { new[] { ObjectId.Empty, ObjectId.Parse("4d0ce088e447ad08b4721a37") }, new[] { "{ $oid: '000000000000000000000000' }", "{ $oid: '4d0ce088e447ad08b4721a37' }" }, Exp(p => p.Id), "_id" }, new object[] { new[] { Guid.Empty, Guid.Parse("b52af144-bc97-454f-a578-418a64fa95bf") }, new[] { """{ "$binary" : { "base64" : "AAAAAAAAAAAAAAAAAAAAAA==", "subType" : "04" } }""", """{ "$binary" : { "base64" : "tSrxRLyXRU+leEGKZPqVvw==", "subType" : "04" } }""" }, Exp(p => p.Guid), nameof(Person.Guid) }, - new object[] { new object[] { (byte)1, (short)2, (int)3 }, new[] { "1", "2", "3" }, Exp(p => p.Object), nameof(Person.Object) } //TODO How do we solve this? Should we actually support this? }; [Fact] @@ -611,7 +615,7 @@ public void In_should_throw_when_values_are_invalid() public void In_should_use_correct_serializers_when_using_attributes_and_expression_path() { var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); - var subjectTyped = CreateSubject(); + var subjectTyped = CreateSubject(); AssertRendered( subjectTyped.In(t => t.DefaultGuid, [testGuid, testGuid]), @@ -626,7 +630,7 @@ public void In_should_use_correct_serializers_when_using_attributes_and_expressi public void In_should_use_correct_serializers_when_using_attributes_and_string_path() { var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); - var subjectTyped = CreateSubject(); + var subjectTyped = CreateSubject(); AssertRendered( subjectTyped.In("DefaultGuid", [testGuid, testGuid]), @@ -643,7 +647,7 @@ public void In_should_use_correct_serializers_when_using_serializer_registry() BsonSerializer.RegisterSerializer(new GuidSerializer(BsonType.String)); var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); - var subjectTyped = CreateSubject(); + var subjectTyped = CreateSubject(); AssertRendered( subjectTyped.In(t => t.UndefinedRepresentationGuid, [testGuid, testGuid]), @@ -1000,9 +1004,54 @@ public void Range_should_render_supported_types( new object[] { (float)1, (float)2, "1", "2", Exp(p => p.Float), nameof(Person.Float) }, new object[] { (double)1, (double)2, "1", "2", Exp(p => p.Double), nameof(Person.Double) }, new object[] { DateTime.MinValue, DateTime.MaxValue, "ISODate(\"0001-01-01T00:00:00Z\")", "ISODate(\"9999-12-31T23:59:59.999Z\")", Exp(p => p.Birthday), "dob" }, - new object[] { DateTimeOffset.MinValue, DateTimeOffset.MaxValue, "ISODate(\"0001-01-01T00:00:00Z\")", "ISODate(\"9999-12-31T23:59:59.999Z\")", Exp(p => p.DateTimeOffset), nameof(Person.DateTimeOffset) } + new object[] { DateTimeOffset.MinValue, DateTimeOffset.MaxValue,"""{ "DateTime" : { "$date" : { "$numberLong" : "-62135596800000" } }, "Ticks" : 0, "Offset" : 0 }""", """{ "DateTime" : { "$date" : "9999-12-31T23:59:59.999Z" }, "Ticks" : 3155378975999999999, "Offset" : 0 }""", Exp(p => p.DateTimeOffset), nameof(Person.DateTimeOffset) } }; - + + [Fact] + public void Range_should_use_correct_serializers_when_using_attributes_and_expression_path() + { + var testDateTime = new DateTimeOffset(new DateTime(2025, 01, 01)); + var subjectTyped = CreateSubject(); + + AssertRendered( + subjectTyped.Range(t => t.DefaultDateTimeOffset, new SearchRange(testDateTime, null, false, false )), + """{"range" :{ "gt" : { "DateTime" : { "$date" : "2024-12-31T23:00:00Z" }, "Ticks" : 638712864000000000, "Offset" : 60 }, "path" : "DefaultDateTimeOffset" }}"""); + + AssertRendered( + subjectTyped.Range(t => t.StringDateTimeOffset, new SearchRange(testDateTime, null, false, false )), + """{"range":{ "gt" : "2025-01-01T00:00:00+01:00", "path" : "StringDateTimeOffset" }}"""); + } + + [Fact] + public void Range_should_use_correct_serializers_when_using_attributes_and_string_path() + { + var testDateTime = new DateTimeOffset(new DateTime(2025, 01, 01)); + var subjectTyped = CreateSubject(); + + AssertRendered( + subjectTyped.Range("DefaultDateTimeOffset", + new SearchRange(testDateTime, null, false, false)), + """{"range" :{ "gt" : { "DateTime" : { "$date" : "2024-12-31T23:00:00Z" }, "Ticks" : 638712864000000000, "Offset" : 60 }, "path" : "DefaultDateTimeOffset" }}"""); + + AssertRendered( + subjectTyped.Range("StringDateTimeOffset", + new SearchRange(testDateTime, null, false, false)), + """{"range":{ "gt" : "2025-01-01T00:00:00+01:00", "path" : "StringDateTimeOffset" }}"""); + } + + [Fact(Skip = "This should only be run manually due to the use of BsonSerializer.RegisterSerializer")] + public void Range_should_use_correct_serializers_when_using_serializer_registry() + { + BsonSerializer.RegisterSerializer(new DateTimeOffsetSerializer(BsonType.String)); + + var testDateTime = new DateTimeOffset(new DateTime(2025, 01, 01)); + var subjectTyped = CreateSubject(); + + AssertRendered( + subjectTyped.Range(t => t.StringDateTimeOffset, new SearchRange(testDateTime, null, false, false )), + """{"range":{ "gt" : "2025-01-01T00:00:00+01:00", "path" : "StringDateTimeOffset" }}"""); + } + [Fact] public void Range_with_array_field_should_render_correctly() { @@ -1378,7 +1427,7 @@ public class SimplestPerson public string FirstName { get; set; } } - public class TestGuidClass + public class AttributesTestClass { [BsonGuidRepresentation(GuidRepresentation.Standard)] public Guid DefaultGuid { get; set; } @@ -1387,6 +1436,11 @@ public class TestGuidClass public Guid StringGuid { get; set; } public Guid UndefinedRepresentationGuid { get; set; } + + public DateTimeOffset DefaultDateTimeOffset { get; set; } + + [BsonRepresentation(BsonType.String)] + public DateTimeOffset StringDateTimeOffset { get; set; } } } } From b67443a19b23bf872bdb10f664a3373a196ee503 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 7 Jan 2025 11:40:10 +0100 Subject: [PATCH 20/42] Small corrections --- .../Search/OperatorSearchDefinitions.cs | 59 ++++++------------- 1 file changed, 18 insertions(+), 41 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index d2f0751037b..12332707b82 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -121,46 +121,31 @@ private protected override BsonDocument RenderArguments(RenderArgs ar internal sealed class EqualsSearchDefinition : OperatorSearchDefinition { - private readonly TField _value; //TODO Consider changing TFIeld to TValue (more correct) - private readonly FieldDefinition _field; - private readonly FieldDefinition> _arrayField; + private readonly TField _value; - //TODO I think that if we use the search definition here we can use only one constructor and uniform it to the other two - public EqualsSearchDefinition(FieldDefinition path, TField value, SearchScoreDefinition score) - : base(OperatorType.Equals, new SingleSearchPathDefinition(path), score) + public EqualsSearchDefinition(FieldDefinition path, TField value, SearchScoreDefinition score) + : base(OperatorType.Equals, path, score) { _value = value; - _field = path; - } - - public EqualsSearchDefinition(FieldDefinition> path, TField value, SearchScoreDefinition score) - : base(OperatorType.Equals, new SingleSearchPathDefinition(path), score) - { - _value = value; - _arrayField = path; } private protected override BsonDocument RenderArguments(RenderArgs args) { - IBsonSerializer valueSerializer; - - if (_field is null) + var searchPathDefinition = (SingleSearchPathDefinition)_path; + var renderedField = searchPathDefinition.Field.Render(args); + var serializer = renderedField.FieldSerializer switch { - var renderedField = _arrayField.Render(args); - valueSerializer = ArraySerializerHelper.GetItemSerializer(renderedField.ValueSerializer); - } - else - { - var renderedField = _field.Render(args); - valueSerializer = renderedField.ValueSerializer; - } + null => BsonSerializer.LookupSerializer(), + IBsonArraySerializer => ArraySerializerHelper.GetItemSerializer(renderedField.FieldSerializer), + _ => renderedField.FieldSerializer + }; var document = new BsonDocument(); using var bsonWriter = new BsonDocumentWriter(document); var context = BsonSerializationContext.CreateRoot(bsonWriter); bsonWriter.WriteStartDocument(); bsonWriter.WriteName("value"); - valueSerializer.Serialize(context, _value); + serializer.Serialize(context, _value); bsonWriter.WriteEndDocument(); return document; @@ -390,22 +375,14 @@ public RangeSearchDefinition( private protected override BsonDocument RenderArguments(RenderArgs args) { - IBsonSerializer serializer; - - if (_path is SingleSearchPathDefinition searchPathDefinition) + var searchPathDefinition = (SingleSearchPathDefinition)_path; + var renderedField = searchPathDefinition.Field.Render(args); + var serializer = renderedField.FieldSerializer switch { - var renderedField = searchPathDefinition.Field.Render(args); - serializer = renderedField.FieldSerializer switch - { - null => BsonSerializer.LookupSerializer(), - IBsonArraySerializer => ArraySerializerHelper.GetItemSerializer(renderedField.FieldSerializer), - _ => renderedField.FieldSerializer - }; - } - else - { - serializer = BsonSerializer.LookupSerializer(); - } + null => BsonSerializer.LookupSerializer(), + IBsonArraySerializer => ArraySerializerHelper.GetItemSerializer(renderedField.FieldSerializer), + _ => renderedField.FieldSerializer + }; var document = new BsonDocument(); using var bsonWriter = new BsonDocumentWriter(document); From 0cae022b8561939e94ff62f64537a7bb35196fc2 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 7 Jan 2025 12:45:16 +0100 Subject: [PATCH 21/42] Small correction to tests --- .../Search/SearchDefinitionBuilderTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs index 5a97cb3bb55..1b641853448 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs @@ -1010,7 +1010,7 @@ public void Range_should_render_supported_types( [Fact] public void Range_should_use_correct_serializers_when_using_attributes_and_expression_path() { - var testDateTime = new DateTimeOffset(new DateTime(2025, 01, 01)); + var testDateTime = new DateTimeOffset(new DateTime(2025, 01, 01, 0, 0, 0, DateTimeKind.Utc)); var subjectTyped = CreateSubject(); AssertRendered( @@ -1025,7 +1025,7 @@ public void Range_should_use_correct_serializers_when_using_attributes_and_expre [Fact] public void Range_should_use_correct_serializers_when_using_attributes_and_string_path() { - var testDateTime = new DateTimeOffset(new DateTime(2025, 01, 01)); + var testDateTime = new DateTimeOffset(new DateTime(2025, 01, 01, 0, 0, 0, DateTimeKind.Utc)); var subjectTyped = CreateSubject(); AssertRendered( @@ -1044,7 +1044,7 @@ public void Range_should_use_correct_serializers_when_using_serializer_registry( { BsonSerializer.RegisterSerializer(new DateTimeOffsetSerializer(BsonType.String)); - var testDateTime = new DateTimeOffset(new DateTime(2025, 01, 01)); + var testDateTime = new DateTimeOffset(new DateTime(2025, 01, 01, 0, 0, 0, DateTimeKind.Utc)); var subjectTyped = CreateSubject(); AssertRendered( From c67288195305fba46412688a7b140b870fe6de5e Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 7 Jan 2025 16:31:51 +0100 Subject: [PATCH 22/42] Test correction --- .../Search/SearchDefinitionBuilderTests.cs | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs index 1b641853448..f46b9da4b23 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs @@ -1010,46 +1010,44 @@ public void Range_should_render_supported_types( [Fact] public void Range_should_use_correct_serializers_when_using_attributes_and_expression_path() { - var testDateTime = new DateTimeOffset(new DateTime(2025, 01, 01, 0, 0, 0, DateTimeKind.Utc)); + var testLong = 23; var subjectTyped = CreateSubject(); AssertRendered( - subjectTyped.Range(t => t.DefaultDateTimeOffset, new SearchRange(testDateTime, null, false, false )), - """{"range" :{ "gt" : { "DateTime" : { "$date" : "2024-12-31T23:00:00Z" }, "Ticks" : 638712864000000000, "Offset" : 60 }, "path" : "DefaultDateTimeOffset" }}"""); + subjectTyped.Range(t => t.DefaultLong, new SearchRange(testLong, null, false, false )), + """{"range" :{ "gt" : 23, "path" : "DefaultLong" }}"""); AssertRendered( - subjectTyped.Range(t => t.StringDateTimeOffset, new SearchRange(testDateTime, null, false, false )), - """{"range":{ "gt" : "2025-01-01T00:00:00+01:00", "path" : "StringDateTimeOffset" }}"""); + subjectTyped.Range(t => t.StringLong, new SearchRange(testLong, null, false, false )), + """{"range":{ "gt" : "23", "path" : "StringLong" }}"""); } [Fact] public void Range_should_use_correct_serializers_when_using_attributes_and_string_path() { - var testDateTime = new DateTimeOffset(new DateTime(2025, 01, 01, 0, 0, 0, DateTimeKind.Utc)); + var testLong = 23; var subjectTyped = CreateSubject(); AssertRendered( - subjectTyped.Range("DefaultDateTimeOffset", - new SearchRange(testDateTime, null, false, false)), - """{"range" :{ "gt" : { "DateTime" : { "$date" : "2024-12-31T23:00:00Z" }, "Ticks" : 638712864000000000, "Offset" : 60 }, "path" : "DefaultDateTimeOffset" }}"""); + subjectTyped.Range("DefaultLong", new SearchRange(testLong, null, false, false )), + """{"range" :{ "gt" : 23, "path" : "DefaultLong" }}"""); AssertRendered( - subjectTyped.Range("StringDateTimeOffset", - new SearchRange(testDateTime, null, false, false)), - """{"range":{ "gt" : "2025-01-01T00:00:00+01:00", "path" : "StringDateTimeOffset" }}"""); + subjectTyped.Range("StringLong", new SearchRange(testLong, null, false, false )), + """{"range":{ "gt" : "23", "path" : "StringLong" }}"""); } [Fact(Skip = "This should only be run manually due to the use of BsonSerializer.RegisterSerializer")] public void Range_should_use_correct_serializers_when_using_serializer_registry() { - BsonSerializer.RegisterSerializer(new DateTimeOffsetSerializer(BsonType.String)); + BsonSerializer.RegisterSerializer(new Int64Serializer(BsonType.String)); - var testDateTime = new DateTimeOffset(new DateTime(2025, 01, 01, 0, 0, 0, DateTimeKind.Utc)); + var testLong = 23; var subjectTyped = CreateSubject(); AssertRendered( - subjectTyped.Range(t => t.StringDateTimeOffset, new SearchRange(testDateTime, null, false, false )), - """{"range":{ "gt" : "2025-01-01T00:00:00+01:00", "path" : "StringDateTimeOffset" }}"""); + subjectTyped.Range(t => t.DefaultLong, new SearchRange(testLong, null, false, false )), + """{"range":{ "gt" : "23", "path" : "DefaultLong" }}"""); } [Fact] @@ -1437,10 +1435,10 @@ public class AttributesTestClass public Guid UndefinedRepresentationGuid { get; set; } - public DateTimeOffset DefaultDateTimeOffset { get; set; } + public long DefaultLong { get; set; } [BsonRepresentation(BsonType.String)] - public DateTimeOffset StringDateTimeOffset { get; set; } + public long StringLong { get; set; } } } } From 8796216787244aa118489595e9670745ea5bbd01 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Wed, 8 Jan 2025 10:44:51 +0100 Subject: [PATCH 23/42] Remove comment --- src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs b/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs index 4d96a0d40b4..09c5172a7f8 100644 --- a/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs +++ b/src/MongoDB.Driver/Search/SearchDefinitionBuilder.cs @@ -119,7 +119,7 @@ public SearchDefinition EmbeddedDocument( /// The score modifier. /// An equality search definition. public SearchDefinition Equals( - FieldDefinition path, //TODO We should try to uniform this to the In operator for next major (this should be SearchPathDefinition) + FieldDefinition path, TField value, SearchScoreDefinition score = null) => new EqualsSearchDefinition(path, value, score); From a8571e5bc04be8e5d6924a64a633a5eadec159f3 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Wed, 8 Jan 2025 11:23:30 +0100 Subject: [PATCH 24/42] Moved method --- src/MongoDB.Driver/FieldValueSerializerHelper.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/MongoDB.Driver/FieldValueSerializerHelper.cs b/src/MongoDB.Driver/FieldValueSerializerHelper.cs index 13f34747052..7782bd98dfb 100644 --- a/src/MongoDB.Driver/FieldValueSerializerHelper.cs +++ b/src/MongoDB.Driver/FieldValueSerializerHelper.cs @@ -323,12 +323,6 @@ public IEnumerableSerializer(IBsonSerializer itemSerializer) _itemSerializer = itemSerializer; } - public bool TryGetItemSerializationInfo(out BsonSerializationInfo serializationInfo) - { - serializationInfo = new BsonSerializationInfo(null, _itemSerializer, typeof(TItem)); - return true; - } - public override bool Equals(object obj) { if (object.ReferenceEquals(obj, null)) { return false; } @@ -358,6 +352,12 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati bsonWriter.WriteEndArray(); } } + + public bool TryGetItemSerializationInfo(out BsonSerializationInfo serializationInfo) + { + serializationInfo = new BsonSerializationInfo(null, _itemSerializer, typeof(TItem)); + return true; + } } internal class NullableEnumConvertingSerializer : SerializerBase> where TFrom : struct where TTo : struct From 640abefff05216ad4578d5481ace4c60fbed021a Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Wed, 8 Jan 2025 11:24:44 +0100 Subject: [PATCH 25/42] Name corrections --- .../Search/OperatorSearchDefinitions.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index 12332707b82..107e36e469a 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -133,7 +133,7 @@ private protected override BsonDocument RenderArguments(RenderArgs ar { var searchPathDefinition = (SingleSearchPathDefinition)_path; var renderedField = searchPathDefinition.Field.Render(args); - var serializer = renderedField.FieldSerializer switch + var valueSerializer = renderedField.FieldSerializer switch { null => BsonSerializer.LookupSerializer(), IBsonArraySerializer => ArraySerializerHelper.GetItemSerializer(renderedField.FieldSerializer), @@ -145,7 +145,7 @@ private protected override BsonDocument RenderArguments(RenderArgs ar var context = BsonSerializationContext.CreateRoot(bsonWriter); bsonWriter.WriteStartDocument(); bsonWriter.WriteName("value"); - serializer.Serialize(context, _value); + valueSerializer.Serialize(context, _value); bsonWriter.WriteEndDocument(); return document; @@ -239,12 +239,12 @@ public InSearchDefinition( private protected override BsonDocument RenderArguments(RenderArgs args) { - IBsonSerializer serializer; + IBsonSerializer valueSerializer; if (_path is SingleSearchPathDefinition searchPathDefinition) { var renderedField = searchPathDefinition.Field.Render(args); - serializer = renderedField.FieldSerializer switch + valueSerializer = renderedField.FieldSerializer switch { null => new ArraySerializer(BsonSerializer.LookupSerializer()), IBsonArraySerializer => renderedField.FieldSerializer, @@ -253,7 +253,7 @@ private protected override BsonDocument RenderArguments(RenderArgs ar } else { - serializer = new ArraySerializer(BsonSerializer.LookupSerializer()); + valueSerializer = new ArraySerializer(BsonSerializer.LookupSerializer()); } var document = new BsonDocument(); @@ -261,7 +261,7 @@ private protected override BsonDocument RenderArguments(RenderArgs ar var context = BsonSerializationContext.CreateRoot(bsonWriter); bsonWriter.WriteStartDocument(); bsonWriter.WriteName("value"); - serializer.Serialize(context, _values); + valueSerializer.Serialize(context, _values); bsonWriter.WriteEndDocument(); return document; @@ -377,7 +377,7 @@ private protected override BsonDocument RenderArguments(RenderArgs ar { var searchPathDefinition = (SingleSearchPathDefinition)_path; var renderedField = searchPathDefinition.Field.Render(args); - var serializer = renderedField.FieldSerializer switch + var valueSerializer = renderedField.FieldSerializer switch { null => BsonSerializer.LookupSerializer(), IBsonArraySerializer => ArraySerializerHelper.GetItemSerializer(renderedField.FieldSerializer), @@ -391,12 +391,12 @@ private protected override BsonDocument RenderArguments(RenderArgs ar if (_range.Min is not null) { bsonWriter.WriteName(_range.IsMinInclusive? "gte" : "gt"); - serializer.Serialize(context, _range.Min); + valueSerializer.Serialize(context, _range.Min); } if (_range.Max is not null) { bsonWriter.WriteName(_range.IsMaxInclusive? "lte" : "lt"); - serializer.Serialize(context, _range.Max); + valueSerializer.Serialize(context, _range.Max); } bsonWriter.WriteEndDocument(); From ed54baed3de21c25cb20a18ff70961d372c6a55d Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Wed, 5 Feb 2025 11:11:27 +0100 Subject: [PATCH 26/42] Fixes according to PR --- src/MongoDB.Driver/FieldValueSerializerHelper.cs | 15 +-------------- src/MongoDB.Driver/Search/SearchDefinition.cs | 2 -- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/MongoDB.Driver/FieldValueSerializerHelper.cs b/src/MongoDB.Driver/FieldValueSerializerHelper.cs index 7782bd98dfb..699b99590bb 100644 --- a/src/MongoDB.Driver/FieldValueSerializerHelper.cs +++ b/src/MongoDB.Driver/FieldValueSerializerHelper.cs @@ -139,20 +139,7 @@ public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSer // if we can't return a value serializer based on the field serializer return a converting serializer return ConvertIfPossibleSerializer.Create(valueType, fieldType, fieldSerializer, serializerRegistry); - } - - //TODO Never used - public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSerializer, IBsonSerializerRegistry serializerRegistry, Type valueType, object value) - { - if (!valueType.GetTypeInfo().IsValueType && value == null) - { - return fieldSerializer; - } - else - { - return GetSerializerForValueType(fieldSerializer, serializerRegistry, valueType, allowScalarValueForArrayField: false); - } - } + }s // private static methods private static bool HasStringRepresentation(IBsonSerializer serializer) diff --git a/src/MongoDB.Driver/Search/SearchDefinition.cs b/src/MongoDB.Driver/Search/SearchDefinition.cs index b5868a8d9ec..d1e16a1ab3a 100644 --- a/src/MongoDB.Driver/Search/SearchDefinition.cs +++ b/src/MongoDB.Driver/Search/SearchDefinition.cs @@ -123,9 +123,7 @@ private protected enum OperatorType QueryString, Range, Regex, - Search, //TODO This is never used, should we remove them? Span, - Term, //TODO This is never used, should we remove them? Text, Wildcard } From b19e5e617d157b35e8c5fb2a3848cd474cf6da15 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Wed, 5 Feb 2025 11:14:52 +0100 Subject: [PATCH 27/42] Small fixes --- src/MongoDB.Driver/FieldValueSerializerHelper.cs | 2 +- .../MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MongoDB.Driver/FieldValueSerializerHelper.cs b/src/MongoDB.Driver/FieldValueSerializerHelper.cs index 699b99590bb..68880f7fe18 100644 --- a/src/MongoDB.Driver/FieldValueSerializerHelper.cs +++ b/src/MongoDB.Driver/FieldValueSerializerHelper.cs @@ -139,7 +139,7 @@ public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSer // if we can't return a value serializer based on the field serializer return a converting serializer return ConvertIfPossibleSerializer.Create(valueType, fieldType, fieldSerializer, serializerRegistry); - }s + } // private static methods private static bool HasStringRepresentation(IBsonSerializer serializer) diff --git a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs index f46b9da4b23..b3a399e4e00 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs @@ -278,7 +278,7 @@ public void Equals_with_array_should_render_supported_type() } [Theory] - [MemberData(nameof(EqualsSupportedTypesTestData))] //TODO Need to fix this + [MemberData(nameof(EqualsSupportedTypesTestData))] public void Equals_should_render_supported_type( T value, string valueRendered, From 53bb632a3e42ec5dc8909fd1749e4ebad06283f2 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Wed, 5 Feb 2025 13:12:46 +0100 Subject: [PATCH 28/42] Corrected TField to TValue for better clarity --- .../Search/OperatorSearchDefinitions.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index 107e36e469a..43a08469caa 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -119,11 +119,11 @@ private protected override BsonDocument RenderArguments(RenderArgs ar } } - internal sealed class EqualsSearchDefinition : OperatorSearchDefinition + internal sealed class EqualsSearchDefinition : OperatorSearchDefinition { - private readonly TField _value; + private readonly TValue _value; - public EqualsSearchDefinition(FieldDefinition path, TField value, SearchScoreDefinition score) + public EqualsSearchDefinition(FieldDefinition path, TValue value, SearchScoreDefinition score) : base(OperatorType.Equals, path, score) { _value = value; @@ -135,7 +135,7 @@ private protected override BsonDocument RenderArguments(RenderArgs ar var renderedField = searchPathDefinition.Field.Render(args); var valueSerializer = renderedField.FieldSerializer switch { - null => BsonSerializer.LookupSerializer(), + null => BsonSerializer.LookupSerializer(), IBsonArraySerializer => ArraySerializerHelper.GetItemSerializer(renderedField.FieldSerializer), _ => renderedField.FieldSerializer }; @@ -223,13 +223,13 @@ private protected override BsonDocument RenderArguments(RenderArgs ar new(_area.Render()); } - internal sealed class InSearchDefinition : OperatorSearchDefinition + internal sealed class InSearchDefinition : OperatorSearchDefinition { - private readonly TField[] _values; + private readonly TValue[] _values; public InSearchDefinition( SearchPathDefinition path, - IEnumerable values, + IEnumerable values, SearchScoreDefinition score) : base(OperatorType.In, path, score) { @@ -246,14 +246,14 @@ private protected override BsonDocument RenderArguments(RenderArgs ar var renderedField = searchPathDefinition.Field.Render(args); valueSerializer = renderedField.FieldSerializer switch { - null => new ArraySerializer(BsonSerializer.LookupSerializer()), + null => new ArraySerializer(BsonSerializer.LookupSerializer()), IBsonArraySerializer => renderedField.FieldSerializer, - _ => new ArraySerializer((IBsonSerializer)renderedField.FieldSerializer) + _ => new ArraySerializer((IBsonSerializer)renderedField.FieldSerializer) }; } else { - valueSerializer = new ArraySerializer(BsonSerializer.LookupSerializer()); + valueSerializer = new ArraySerializer(BsonSerializer.LookupSerializer()); } var document = new BsonDocument(); @@ -359,14 +359,14 @@ private protected override BsonDocument RenderArguments(RenderArgs ar }; } - internal sealed class RangeSearchDefinition : OperatorSearchDefinition - where TField : struct, IComparable + internal sealed class RangeSearchDefinition : OperatorSearchDefinition + where TValue : struct, IComparable { - private readonly SearchRange _range; + private readonly SearchRange _range; public RangeSearchDefinition( SearchPathDefinition path, - SearchRange range, + SearchRange range, SearchScoreDefinition score) : base(OperatorType.Range, path, score) { @@ -379,7 +379,7 @@ private protected override BsonDocument RenderArguments(RenderArgs ar var renderedField = searchPathDefinition.Field.Render(args); var valueSerializer = renderedField.FieldSerializer switch { - null => BsonSerializer.LookupSerializer(), + null => BsonSerializer.LookupSerializer(), IBsonArraySerializer => ArraySerializerHelper.GetItemSerializer(renderedField.FieldSerializer), _ => renderedField.FieldSerializer }; From d84f2908018e532d068ebb16df5a32f525a40c58 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Wed, 5 Feb 2025 15:35:40 +0100 Subject: [PATCH 29/42] Small fix --- src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs b/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs index 6088ab13dce..61bc3e4be85 100644 --- a/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs +++ b/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs @@ -113,7 +113,7 @@ public AnalyzerSearchPathDefinition(FieldDefinition field, string ana } public override BsonValue Render(RenderArgs args) => - new BsonDocument() + new BsonDocument { { "value", RenderField(_field, args) }, { "multi", _analyzerName } From 993832bd4ee3332d66f0ddb40a495aef808ae717 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Thu, 6 Feb 2025 17:41:52 +0100 Subject: [PATCH 30/42] Correction to avoid path rendering twice --- .../Search/OperatorSearchDefinitions.cs | 76 +++++++++++-------- src/MongoDB.Driver/Search/SearchDefinition.cs | 21 ++++- .../Search/SearchPathDefinition.cs | 6 +- 3 files changed, 66 insertions(+), 37 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index 43a08469caa..5c62640ce9c 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -45,7 +45,8 @@ public AutocompleteSearchDefinition( _fuzzy = fuzzy; } - private protected override BsonDocument RenderArguments(RenderArgs args) => + private protected override BsonDocument RenderArguments(RenderArgs args, + RenderedFieldDefinition renderedFieldDefinition = null) => new() { { "query", _query.Render() }, @@ -79,7 +80,8 @@ public CompoundSearchDefinition( _minimumShouldMatch = minimumShouldMatch; } - private protected override BsonDocument RenderArguments(RenderArgs args) + private protected override BsonDocument RenderArguments(RenderArgs args, + RenderedFieldDefinition renderedFieldDefinition = null) { return new() { @@ -107,7 +109,8 @@ public EmbeddedDocumentSearchDefinition(FieldDefinition args) + private protected override BsonDocument RenderArguments(RenderArgs args, + RenderedFieldDefinition renderedFieldDefinition = null) { // Add base path to all nested operator paths var pathPrefix = _path.Render(args).AsString; @@ -129,15 +132,14 @@ public EqualsSearchDefinition(FieldDefinition path, TValue value, Sea _value = value; } - private protected override BsonDocument RenderArguments(RenderArgs args) + private protected override BsonDocument RenderArguments(RenderArgs args, + RenderedFieldDefinition renderedFieldDefinition = null) { - var searchPathDefinition = (SingleSearchPathDefinition)_path; - var renderedField = searchPathDefinition.Field.Render(args); - var valueSerializer = renderedField.FieldSerializer switch + var valueSerializer = renderedFieldDefinition!.FieldSerializer switch { null => BsonSerializer.LookupSerializer(), - IBsonArraySerializer => ArraySerializerHelper.GetItemSerializer(renderedField.FieldSerializer), - _ => renderedField.FieldSerializer + IBsonArraySerializer => ArraySerializerHelper.GetItemSerializer(renderedFieldDefinition.FieldSerializer), + _ => renderedFieldDefinition.FieldSerializer }; var document = new BsonDocument(); @@ -172,7 +174,8 @@ public FacetSearchDefinition(SearchDefinition @operator, IEnumerable< _facets = Ensure.IsNotNull(facets, nameof(facets)).ToArray(); } - private protected override BsonDocument RenderArguments(RenderArgs args) => + private protected override BsonDocument RenderArguments(RenderArgs args, + RenderedFieldDefinition renderedFieldDefinition = null) => new() { { "operator", _operator.Render(args) }, @@ -197,7 +200,8 @@ public GeoShapeSearchDefinition( _relation = relation; } - private protected override BsonDocument RenderArguments(RenderArgs args) => + private protected override BsonDocument RenderArguments(RenderArgs args, + RenderedFieldDefinition renderedFieldDefinition = null) => new() { { "geometry", _geometry.ToBsonDocument() }, @@ -219,7 +223,8 @@ public GeoWithinSearchDefinition( _area = Ensure.IsNotNull(area, nameof(area)); } - private protected override BsonDocument RenderArguments(RenderArgs args) => + private protected override BsonDocument RenderArguments(RenderArgs args, + RenderedFieldDefinition renderedFieldDefinition = null) => new(_area.Render()); } @@ -237,18 +242,18 @@ public InSearchDefinition( _values = values.ToArray(); } - private protected override BsonDocument RenderArguments(RenderArgs args) + private protected override BsonDocument RenderArguments(RenderArgs args, + RenderedFieldDefinition renderedFieldDefinition = null) { IBsonSerializer valueSerializer; - if (_path is SingleSearchPathDefinition searchPathDefinition) + if (_path is SingleSearchPathDefinition) { - var renderedField = searchPathDefinition.Field.Render(args); - valueSerializer = renderedField.FieldSerializer switch + valueSerializer = renderedFieldDefinition!.FieldSerializer switch { null => new ArraySerializer(BsonSerializer.LookupSerializer()), - IBsonArraySerializer => renderedField.FieldSerializer, - _ => new ArraySerializer((IBsonSerializer)renderedField.FieldSerializer) + IBsonArraySerializer => renderedFieldDefinition.FieldSerializer, + _ => new ArraySerializer((IBsonSerializer)renderedFieldDefinition.FieldSerializer) }; } else @@ -278,7 +283,8 @@ public MoreLikeThisSearchDefinition(IEnumerable like) _like = Ensure.IsNotNull(like, nameof(like)).ToArray(); } - private protected override BsonDocument RenderArguments(RenderArgs args) + private protected override BsonDocument RenderArguments(RenderArgs args, + RenderedFieldDefinition renderedFieldDefinition = null) { var likeSerializer = typeof(TLike) switch { @@ -307,7 +313,8 @@ public NearSearchDefinition( _pivot = pivot; } - private protected override BsonDocument RenderArguments(RenderArgs args) => + private protected override BsonDocument RenderArguments(RenderArgs args, + RenderedFieldDefinition renderedFieldDefinition = null) => new() { { "origin", _origin }, @@ -331,7 +338,8 @@ public PhraseSearchDefinition( _slop = slop; } - private protected override BsonDocument RenderArguments(RenderArgs args) => + private protected override BsonDocument RenderArguments(RenderArgs args, + RenderedFieldDefinition renderedFieldDefinition = null) => new() { { "query", _query.Render() }, @@ -351,7 +359,8 @@ public QueryStringSearchDefinition(FieldDefinition defaultPath, strin _query = Ensure.IsNotNull(query, nameof(query)); } - private protected override BsonDocument RenderArguments(RenderArgs args) => + private protected override BsonDocument RenderArguments(RenderArgs args, + RenderedFieldDefinition renderedFieldDefinition = null) => new() { { "defaultPath", _defaultPath.Render(args) }, @@ -373,15 +382,14 @@ public RangeSearchDefinition( _range = range; } - private protected override BsonDocument RenderArguments(RenderArgs args) + private protected override BsonDocument RenderArguments(RenderArgs args, + RenderedFieldDefinition renderedFieldDefinition = null) { - var searchPathDefinition = (SingleSearchPathDefinition)_path; - var renderedField = searchPathDefinition.Field.Render(args); - var valueSerializer = renderedField.FieldSerializer switch + var valueSerializer = renderedFieldDefinition!.FieldSerializer switch { null => BsonSerializer.LookupSerializer(), - IBsonArraySerializer => ArraySerializerHelper.GetItemSerializer(renderedField.FieldSerializer), - _ => renderedField.FieldSerializer + IBsonArraySerializer => ArraySerializerHelper.GetItemSerializer(renderedFieldDefinition.FieldSerializer), + _ => renderedFieldDefinition.FieldSerializer }; var document = new BsonDocument(); @@ -420,7 +428,8 @@ public RegexSearchDefinition( _allowAnalyzedField = allowAnalyzedField; } - private protected override BsonDocument RenderArguments(RenderArgs args) => + private protected override BsonDocument RenderArguments(RenderArgs args, + RenderedFieldDefinition renderedFieldDefinition = null) => new() { { "query", _query.Render() }, @@ -438,7 +447,8 @@ public SpanSearchDefinition(SearchSpanDefinition clause) _clause = Ensure.IsNotNull(clause, nameof(clause)); } - private protected override BsonDocument RenderArguments(RenderArgs args) => + private protected override BsonDocument RenderArguments(RenderArgs args, + RenderedFieldDefinition renderedFieldDefinition = null) => _clause.Render(args); } @@ -461,7 +471,8 @@ public TextSearchDefinition( _synonyms = synonyms; } - private protected override BsonDocument RenderArguments(RenderArgs args) => + private protected override BsonDocument RenderArguments(RenderArgs args, + RenderedFieldDefinition renderedFieldDefinition = null) => new() { { "query", _query.Render() }, @@ -486,7 +497,8 @@ public WildcardSearchDefinition( _allowAnalyzedField = allowAnalyzedField; } - private protected override BsonDocument RenderArguments(RenderArgs args) => + private protected override BsonDocument RenderArguments(RenderArgs args, + RenderedFieldDefinition renderedFieldDefinition = null) => new() { { "query", _query.Render() }, diff --git a/src/MongoDB.Driver/Search/SearchDefinition.cs b/src/MongoDB.Driver/Search/SearchDefinition.cs index d1e16a1ab3a..e4455009151 100644 --- a/src/MongoDB.Driver/Search/SearchDefinition.cs +++ b/src/MongoDB.Driver/Search/SearchDefinition.cs @@ -154,13 +154,26 @@ private protected OperatorSearchDefinition(OperatorType operatorType, SearchPath /// public override BsonDocument Render(RenderArgs args) { - var renderedArgs = RenderArguments(args); - renderedArgs.Add("path", () => _path.Render(args), _path != null); - renderedArgs.Add("score", () => _score.Render(args), _score != null); + BsonDocument renderedArgs; + + if (_path is SingleSearchPathDefinition singleSearchPathDefinition) + { + // Special case for SingleSearchPathDefinition in order to avoid rendering the path twice + var (renderedPath, renderedField) = _path.GetRenderedFieldAndStringPath(singleSearchPathDefinition.Field, args); + renderedArgs = RenderArguments(args, renderedField); + renderedArgs.Add("path", renderedPath); + } + else + { + renderedArgs = RenderArguments(args); + renderedArgs.Add("path", () => _path.Render(args), _path != null); + } + renderedArgs.Add("score", () => _score.Render(args), _score != null); return new(_operatorType.ToCamelCase(), renderedArgs); } - private protected virtual BsonDocument RenderArguments(RenderArgs args) => new(); + private protected virtual BsonDocument RenderArguments(RenderArgs args, + RenderedFieldDefinition renderedFieldDefinition = null) => new(); } } diff --git a/src/MongoDB.Driver/Search/SearchPathDefinition.cs b/src/MongoDB.Driver/Search/SearchPathDefinition.cs index 3b7aad231f9..4115f3e5916 100644 --- a/src/MongoDB.Driver/Search/SearchPathDefinition.cs +++ b/src/MongoDB.Driver/Search/SearchPathDefinition.cs @@ -104,11 +104,15 @@ public static implicit operator SearchPathDefinition(List fie /// The render arguments. /// The rendered field. protected string RenderField(FieldDefinition fieldDefinition, RenderArgs args) + => GetRenderedFieldAndStringPath(fieldDefinition, args).renderedPath; + + internal (string renderedPath, RenderedFieldDefinition renderedFieldDefinition) GetRenderedFieldAndStringPath(FieldDefinition fieldDefinition, RenderArgs args) { var renderedField = fieldDefinition.Render(args); var prefix = args.PathRenderArgs.PathPrefix; + var renderedString = prefix == null ? renderedField.FieldName : $"{prefix}.{renderedField.FieldName}"; - return prefix == null ? renderedField.FieldName : $"{prefix}.{renderedField.FieldName}"; + return (renderedString, renderedField); } } } From a99e7852da669d56530db503abd63b60e5844585 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Thu, 6 Feb 2025 17:55:07 +0100 Subject: [PATCH 31/42] Small fix --- .../Search/OperatorSearchDefinitions.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index 5c62640ce9c..13d63cabf94 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -385,12 +385,21 @@ public RangeSearchDefinition( private protected override BsonDocument RenderArguments(RenderArgs args, RenderedFieldDefinition renderedFieldDefinition = null) { - var valueSerializer = renderedFieldDefinition!.FieldSerializer switch + IBsonSerializer valueSerializer; + + if (_path is SingleSearchPathDefinition) { - null => BsonSerializer.LookupSerializer(), - IBsonArraySerializer => ArraySerializerHelper.GetItemSerializer(renderedFieldDefinition.FieldSerializer), - _ => renderedFieldDefinition.FieldSerializer - }; + valueSerializer = renderedFieldDefinition!.FieldSerializer switch + { + null => BsonSerializer.LookupSerializer(), + IBsonArraySerializer => ArraySerializerHelper.GetItemSerializer(renderedFieldDefinition.FieldSerializer), + _ => renderedFieldDefinition.FieldSerializer + }; + } + else + { + valueSerializer = BsonSerializer.LookupSerializer(); + } var document = new BsonDocument(); using var bsonWriter = new BsonDocumentWriter(document); From 4afcaef38b2b306601a7a6f6d5458da3131f2f67 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Thu, 6 Feb 2025 17:59:08 +0100 Subject: [PATCH 32/42] Small fix --- .../Search/OperatorSearchDefinitions.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index 13d63cabf94..a344de5b3c1 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -135,12 +135,21 @@ public EqualsSearchDefinition(FieldDefinition path, TValue value, Sea private protected override BsonDocument RenderArguments(RenderArgs args, RenderedFieldDefinition renderedFieldDefinition = null) { - var valueSerializer = renderedFieldDefinition!.FieldSerializer switch + IBsonSerializer valueSerializer; + + if (_path is SingleSearchPathDefinition) { - null => BsonSerializer.LookupSerializer(), - IBsonArraySerializer => ArraySerializerHelper.GetItemSerializer(renderedFieldDefinition.FieldSerializer), - _ => renderedFieldDefinition.FieldSerializer - }; + valueSerializer = renderedFieldDefinition!.FieldSerializer switch + { + null => BsonSerializer.LookupSerializer(), + IBsonArraySerializer => ArraySerializerHelper.GetItemSerializer(renderedFieldDefinition.FieldSerializer), + _ => renderedFieldDefinition.FieldSerializer + }; + } + else + { + valueSerializer = BsonSerializer.LookupSerializer(); + } var document = new BsonDocument(); using var bsonWriter = new BsonDocumentWriter(document); From 166edbf39c2de29c3f96a6883dcb91c6477ebef2 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Thu, 6 Feb 2025 20:38:57 +0100 Subject: [PATCH 33/42] Fixes according to PR --- .../Search/OperatorSearchDefinitions.cs | 91 +++++++------------ src/MongoDB.Driver/Search/SearchDefinition.cs | 5 +- 2 files changed, 35 insertions(+), 61 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index a344de5b3c1..353c08cac3e 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -46,7 +46,7 @@ public AutocompleteSearchDefinition( } private protected override BsonDocument RenderArguments(RenderArgs args, - RenderedFieldDefinition renderedFieldDefinition = null) => + IBsonSerializer fieldSerializer = null) => new() { { "query", _query.Render() }, @@ -81,7 +81,7 @@ public CompoundSearchDefinition( } private protected override BsonDocument RenderArguments(RenderArgs args, - RenderedFieldDefinition renderedFieldDefinition = null) + IBsonSerializer fieldSerializer = null) { return new() { @@ -110,7 +110,7 @@ public EmbeddedDocumentSearchDefinition(FieldDefinition args, - RenderedFieldDefinition renderedFieldDefinition = null) + IBsonSerializer fieldSerializer = null) { // Add base path to all nested operator paths var pathPrefix = _path.Render(args).AsString; @@ -133,23 +133,14 @@ public EqualsSearchDefinition(FieldDefinition path, TValue value, Sea } private protected override BsonDocument RenderArguments(RenderArgs args, - RenderedFieldDefinition renderedFieldDefinition = null) + IBsonSerializer fieldSerializer = null) { - IBsonSerializer valueSerializer; - - if (_path is SingleSearchPathDefinition) - { - valueSerializer = renderedFieldDefinition!.FieldSerializer switch - { - null => BsonSerializer.LookupSerializer(), - IBsonArraySerializer => ArraySerializerHelper.GetItemSerializer(renderedFieldDefinition.FieldSerializer), - _ => renderedFieldDefinition.FieldSerializer - }; - } - else + var valueSerializer = fieldSerializer switch { - valueSerializer = BsonSerializer.LookupSerializer(); - } + null => args.SerializerRegistry.GetSerializer(), + IBsonArraySerializer => ArraySerializerHelper.GetItemSerializer(fieldSerializer), + _ => fieldSerializer + }; var document = new BsonDocument(); using var bsonWriter = new BsonDocumentWriter(document); @@ -184,7 +175,7 @@ public FacetSearchDefinition(SearchDefinition @operator, IEnumerable< } private protected override BsonDocument RenderArguments(RenderArgs args, - RenderedFieldDefinition renderedFieldDefinition = null) => + IBsonSerializer fieldSerializer = null) => new() { { "operator", _operator.Render(args) }, @@ -210,7 +201,7 @@ public GeoShapeSearchDefinition( } private protected override BsonDocument RenderArguments(RenderArgs args, - RenderedFieldDefinition renderedFieldDefinition = null) => + IBsonSerializer fieldSerializer = null) => new() { { "geometry", _geometry.ToBsonDocument() }, @@ -233,7 +224,7 @@ public GeoWithinSearchDefinition( } private protected override BsonDocument RenderArguments(RenderArgs args, - RenderedFieldDefinition renderedFieldDefinition = null) => + IBsonSerializer fieldSerializer = null) => new(_area.Render()); } @@ -252,23 +243,14 @@ public InSearchDefinition( } private protected override BsonDocument RenderArguments(RenderArgs args, - RenderedFieldDefinition renderedFieldDefinition = null) + IBsonSerializer fieldSerializer = null) { - IBsonSerializer valueSerializer; - - if (_path is SingleSearchPathDefinition) - { - valueSerializer = renderedFieldDefinition!.FieldSerializer switch - { - null => new ArraySerializer(BsonSerializer.LookupSerializer()), - IBsonArraySerializer => renderedFieldDefinition.FieldSerializer, - _ => new ArraySerializer((IBsonSerializer)renderedFieldDefinition.FieldSerializer) - }; - } - else + var valueSerializer = fieldSerializer switch { - valueSerializer = new ArraySerializer(BsonSerializer.LookupSerializer()); - } + null => new ArraySerializer(args.SerializerRegistry.GetSerializer()), + IBsonArraySerializer => fieldSerializer, + _ => new ArraySerializer((IBsonSerializer)fieldSerializer) + }; var document = new BsonDocument(); using var bsonWriter = new BsonDocumentWriter(document); @@ -293,7 +275,7 @@ public MoreLikeThisSearchDefinition(IEnumerable like) } private protected override BsonDocument RenderArguments(RenderArgs args, - RenderedFieldDefinition renderedFieldDefinition = null) + IBsonSerializer fieldSerializer = null) { var likeSerializer = typeof(TLike) switch { @@ -323,7 +305,7 @@ public NearSearchDefinition( } private protected override BsonDocument RenderArguments(RenderArgs args, - RenderedFieldDefinition renderedFieldDefinition = null) => + IBsonSerializer fieldSerializer = null) => new() { { "origin", _origin }, @@ -348,7 +330,7 @@ public PhraseSearchDefinition( } private protected override BsonDocument RenderArguments(RenderArgs args, - RenderedFieldDefinition renderedFieldDefinition = null) => + IBsonSerializer fieldSerializer = null) => new() { { "query", _query.Render() }, @@ -369,7 +351,7 @@ public QueryStringSearchDefinition(FieldDefinition defaultPath, strin } private protected override BsonDocument RenderArguments(RenderArgs args, - RenderedFieldDefinition renderedFieldDefinition = null) => + IBsonSerializer fieldSerializer = null) => new() { { "defaultPath", _defaultPath.Render(args) }, @@ -392,23 +374,14 @@ public RangeSearchDefinition( } private protected override BsonDocument RenderArguments(RenderArgs args, - RenderedFieldDefinition renderedFieldDefinition = null) + IBsonSerializer fieldSerializer = null) { - IBsonSerializer valueSerializer; - - if (_path is SingleSearchPathDefinition) + var valueSerializer = fieldSerializer switch { - valueSerializer = renderedFieldDefinition!.FieldSerializer switch - { - null => BsonSerializer.LookupSerializer(), - IBsonArraySerializer => ArraySerializerHelper.GetItemSerializer(renderedFieldDefinition.FieldSerializer), - _ => renderedFieldDefinition.FieldSerializer - }; - } - else - { - valueSerializer = BsonSerializer.LookupSerializer(); - } + null => args.SerializerRegistry.GetSerializer(), + IBsonArraySerializer => ArraySerializerHelper.GetItemSerializer(fieldSerializer), + _ => fieldSerializer + }; var document = new BsonDocument(); using var bsonWriter = new BsonDocumentWriter(document); @@ -447,7 +420,7 @@ public RegexSearchDefinition( } private protected override BsonDocument RenderArguments(RenderArgs args, - RenderedFieldDefinition renderedFieldDefinition = null) => + IBsonSerializer fieldSerializer = null) => new() { { "query", _query.Render() }, @@ -466,7 +439,7 @@ public SpanSearchDefinition(SearchSpanDefinition clause) } private protected override BsonDocument RenderArguments(RenderArgs args, - RenderedFieldDefinition renderedFieldDefinition = null) => + IBsonSerializer fieldSerializer = null) => _clause.Render(args); } @@ -490,7 +463,7 @@ public TextSearchDefinition( } private protected override BsonDocument RenderArguments(RenderArgs args, - RenderedFieldDefinition renderedFieldDefinition = null) => + IBsonSerializer fieldSerializer = null) => new() { { "query", _query.Render() }, @@ -516,7 +489,7 @@ public WildcardSearchDefinition( } private protected override BsonDocument RenderArguments(RenderArgs args, - RenderedFieldDefinition renderedFieldDefinition = null) => + IBsonSerializer fieldSerializer = null) => new() { { "query", _query.Render() }, diff --git a/src/MongoDB.Driver/Search/SearchDefinition.cs b/src/MongoDB.Driver/Search/SearchDefinition.cs index e4455009151..73c67a1a596 100644 --- a/src/MongoDB.Driver/Search/SearchDefinition.cs +++ b/src/MongoDB.Driver/Search/SearchDefinition.cs @@ -14,6 +14,7 @@ */ using MongoDB.Bson; +using MongoDB.Bson.Serialization; using MongoDB.Driver.Core.Misc; namespace MongoDB.Driver.Search @@ -160,7 +161,7 @@ public override BsonDocument Render(RenderArgs args) { // Special case for SingleSearchPathDefinition in order to avoid rendering the path twice var (renderedPath, renderedField) = _path.GetRenderedFieldAndStringPath(singleSearchPathDefinition.Field, args); - renderedArgs = RenderArguments(args, renderedField); + renderedArgs = RenderArguments(args, renderedField.FieldSerializer); renderedArgs.Add("path", renderedPath); } else @@ -174,6 +175,6 @@ public override BsonDocument Render(RenderArgs args) } private protected virtual BsonDocument RenderArguments(RenderArgs args, - RenderedFieldDefinition renderedFieldDefinition = null) => new(); + IBsonSerializer fieldSerializer = null) => new(); } } From eb4cb3f190c94f85151a19957f9e83d969eef632 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Thu, 6 Feb 2025 21:24:44 +0100 Subject: [PATCH 34/42] Improvement --- src/MongoDB.Driver/Search/SearchDefinition.cs | 12 +++++------- src/MongoDB.Driver/Search/SearchPathDefinition.cs | 9 ++++++--- .../Search/SearchPathDefinitionBuilder.cs | 6 ++++-- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/MongoDB.Driver/Search/SearchDefinition.cs b/src/MongoDB.Driver/Search/SearchDefinition.cs index 73c67a1a596..4f9bc49e98e 100644 --- a/src/MongoDB.Driver/Search/SearchDefinition.cs +++ b/src/MongoDB.Driver/Search/SearchDefinition.cs @@ -157,17 +157,15 @@ public override BsonDocument Render(RenderArgs args) { BsonDocument renderedArgs; - if (_path is SingleSearchPathDefinition singleSearchPathDefinition) + if (_path is null) { - // Special case for SingleSearchPathDefinition in order to avoid rendering the path twice - var (renderedPath, renderedField) = _path.GetRenderedFieldAndStringPath(singleSearchPathDefinition.Field, args); - renderedArgs = RenderArguments(args, renderedField.FieldSerializer); - renderedArgs.Add("path", renderedPath); + renderedArgs = RenderArguments(args); } else { - renderedArgs = RenderArguments(args); - renderedArgs.Add("path", () => _path.Render(args), _path != null); + var (renderedPath, fieldSerializer) = _path.RenderAndGetFieldSerializer(args); + renderedArgs = RenderArguments(args, fieldSerializer); + renderedArgs.Add("path", renderedPath); } renderedArgs.Add("score", () => _score.Render(args), _score != null); diff --git a/src/MongoDB.Driver/Search/SearchPathDefinition.cs b/src/MongoDB.Driver/Search/SearchPathDefinition.cs index 4115f3e5916..5411d1ab5f6 100644 --- a/src/MongoDB.Driver/Search/SearchPathDefinition.cs +++ b/src/MongoDB.Driver/Search/SearchPathDefinition.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using System.Linq; using MongoDB.Bson; +using MongoDB.Bson.Serialization; namespace MongoDB.Driver.Search { @@ -104,15 +105,17 @@ public static implicit operator SearchPathDefinition(List fie /// The render arguments. /// The rendered field. protected string RenderField(FieldDefinition fieldDefinition, RenderArgs args) - => GetRenderedFieldAndStringPath(fieldDefinition, args).renderedPath; + => RenderFieldWithSerializer(fieldDefinition, args).renderedPath; - internal (string renderedPath, RenderedFieldDefinition renderedFieldDefinition) GetRenderedFieldAndStringPath(FieldDefinition fieldDefinition, RenderArgs args) + internal virtual (BsonValue, IBsonSerializer) RenderAndGetFieldSerializer(RenderArgs args) => (Render(args), null); + + internal (string renderedPath, IBsonSerializer fieldSerializer) RenderFieldWithSerializer(FieldDefinition fieldDefinition, RenderArgs args) { var renderedField = fieldDefinition.Render(args); var prefix = args.PathRenderArgs.PathPrefix; var renderedString = prefix == null ? renderedField.FieldName : $"{prefix}.{renderedField.FieldName}"; - return (renderedString, renderedField); + return (renderedString, renderedField.FieldSerializer); } } } diff --git a/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs b/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs index 61bc3e4be85..ca4ac88ca17 100644 --- a/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs +++ b/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs @@ -18,6 +18,7 @@ using System.Linq; using System.Linq.Expressions; using MongoDB.Bson; +using MongoDB.Bson.Serialization; using MongoDB.Driver.Core.Misc; namespace MongoDB.Driver.Search @@ -137,8 +138,6 @@ internal sealed class SingleSearchPathDefinition : SearchPathDefiniti { private readonly FieldDefinition _field; - public FieldDefinition Field => _field; - public SingleSearchPathDefinition(FieldDefinition field) { _field = Ensure.IsNotNull(field, nameof(field)); @@ -146,6 +145,9 @@ public SingleSearchPathDefinition(FieldDefinition field) public override BsonValue Render(RenderArgs args) => RenderField(_field, args); + + internal override (BsonValue, IBsonSerializer) RenderAndGetFieldSerializer(RenderArgs args) + => RenderFieldWithSerializer(_field, args); } internal sealed class WildcardSearchPathDefinition : SearchPathDefinition From 82e2675a2b42457f805453603dd9c2da3b17b93e Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Fri, 7 Feb 2025 07:30:20 +0100 Subject: [PATCH 35/42] Small corrections according to PR --- src/MongoDB.Driver/Search/SearchPathDefinition.cs | 4 ++-- src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs | 2 +- .../Search/SearchDefinitionBuilderTests.cs | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/MongoDB.Driver/Search/SearchPathDefinition.cs b/src/MongoDB.Driver/Search/SearchPathDefinition.cs index 5411d1ab5f6..fb5ee49e3a4 100644 --- a/src/MongoDB.Driver/Search/SearchPathDefinition.cs +++ b/src/MongoDB.Driver/Search/SearchPathDefinition.cs @@ -105,11 +105,11 @@ public static implicit operator SearchPathDefinition(List fie /// The render arguments. /// The rendered field. protected string RenderField(FieldDefinition fieldDefinition, RenderArgs args) - => RenderFieldWithSerializer(fieldDefinition, args).renderedPath; + => RenderFieldAndGetFieldSerializer(fieldDefinition, args).renderedPath; internal virtual (BsonValue, IBsonSerializer) RenderAndGetFieldSerializer(RenderArgs args) => (Render(args), null); - internal (string renderedPath, IBsonSerializer fieldSerializer) RenderFieldWithSerializer(FieldDefinition fieldDefinition, RenderArgs args) + internal (string renderedPath, IBsonSerializer fieldSerializer) RenderFieldAndGetFieldSerializer(FieldDefinition fieldDefinition, RenderArgs args) { var renderedField = fieldDefinition.Render(args); var prefix = args.PathRenderArgs.PathPrefix; diff --git a/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs b/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs index ca4ac88ca17..aa4160b336d 100644 --- a/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs +++ b/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs @@ -147,7 +147,7 @@ public override BsonValue Render(RenderArgs args) => RenderField(_field, args); internal override (BsonValue, IBsonSerializer) RenderAndGetFieldSerializer(RenderArgs args) - => RenderFieldWithSerializer(_field, args); + => RenderFieldAndGetFieldSerializer(_field, args); } internal sealed class WildcardSearchPathDefinition : SearchPathDefinition diff --git a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs index b3a399e4e00..cad7ab6981f 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs @@ -288,7 +288,8 @@ public void Equals_should_render_supported_type( var subject = CreateSubject(); var subjectTyped = CreateSubject(); - //There is no property called "x" where to pick up a properly configured GuidSerializer for the tests + //When using an untyped query, the default GuidSerializer is used, and that will throw an exception + //because the GuidRepresentation is Unspecified. if (typeof(T) != typeof(Guid)) { AssertRendered( From 1f29e6abe39b59b33ddb9d1c9e25adad060f2842 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 11 Feb 2025 09:38:05 +0100 Subject: [PATCH 36/42] Fixed indentation --- .../Search/OperatorSearchDefinitions.cs | 86 ++++++++++--------- src/MongoDB.Driver/Search/SearchDefinition.cs | 4 +- 2 files changed, 49 insertions(+), 41 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index 353c08cac3e..01148c02e97 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -45,8 +45,9 @@ public AutocompleteSearchDefinition( _fuzzy = fuzzy; } - private protected override BsonDocument RenderArguments(RenderArgs args, - IBsonSerializer fieldSerializer = null) => + private protected override BsonDocument RenderArguments( + RenderArgs args, + IBsonSerializer fieldSerializer) => new() { { "query", _query.Render() }, @@ -80,8 +81,9 @@ public CompoundSearchDefinition( _minimumShouldMatch = minimumShouldMatch; } - private protected override BsonDocument RenderArguments(RenderArgs args, - IBsonSerializer fieldSerializer = null) + private protected override BsonDocument RenderArguments( + RenderArgs args, + IBsonSerializer fieldSerializer) { return new() { @@ -109,8 +111,9 @@ public EmbeddedDocumentSearchDefinition(FieldDefinition args, - IBsonSerializer fieldSerializer = null) + private protected override BsonDocument RenderArguments( + RenderArgs args, + IBsonSerializer fieldSerializer) { // Add base path to all nested operator paths var pathPrefix = _path.Render(args).AsString; @@ -132,8 +135,9 @@ public EqualsSearchDefinition(FieldDefinition path, TValue value, Sea _value = value; } - private protected override BsonDocument RenderArguments(RenderArgs args, - IBsonSerializer fieldSerializer = null) + private protected override BsonDocument RenderArguments( + RenderArgs args, + IBsonSerializer fieldSerializer) { var valueSerializer = fieldSerializer switch { @@ -174,8 +178,9 @@ public FacetSearchDefinition(SearchDefinition @operator, IEnumerable< _facets = Ensure.IsNotNull(facets, nameof(facets)).ToArray(); } - private protected override BsonDocument RenderArguments(RenderArgs args, - IBsonSerializer fieldSerializer = null) => + private protected override BsonDocument RenderArguments( + RenderArgs args, + IBsonSerializer fieldSerializer) => new() { { "operator", _operator.Render(args) }, @@ -200,8 +205,9 @@ public GeoShapeSearchDefinition( _relation = relation; } - private protected override BsonDocument RenderArguments(RenderArgs args, - IBsonSerializer fieldSerializer = null) => + private protected override BsonDocument RenderArguments( + RenderArgs args, + IBsonSerializer fieldSerializer) => new() { { "geometry", _geometry.ToBsonDocument() }, @@ -223,8 +229,9 @@ public GeoWithinSearchDefinition( _area = Ensure.IsNotNull(area, nameof(area)); } - private protected override BsonDocument RenderArguments(RenderArgs args, - IBsonSerializer fieldSerializer = null) => + private protected override BsonDocument RenderArguments( + RenderArgs args, + IBsonSerializer fieldSerializer) => new(_area.Render()); } @@ -242,8 +249,9 @@ public InSearchDefinition( _values = values.ToArray(); } - private protected override BsonDocument RenderArguments(RenderArgs args, - IBsonSerializer fieldSerializer = null) + private protected override BsonDocument RenderArguments( + RenderArgs args, + IBsonSerializer fieldSerializer) { var valueSerializer = fieldSerializer switch { @@ -274,8 +282,9 @@ public MoreLikeThisSearchDefinition(IEnumerable like) _like = Ensure.IsNotNull(like, nameof(like)).ToArray(); } - private protected override BsonDocument RenderArguments(RenderArgs args, - IBsonSerializer fieldSerializer = null) + private protected override BsonDocument RenderArguments( + RenderArgs args, + IBsonSerializer fieldSerializer) { var likeSerializer = typeof(TLike) switch { @@ -304,9 +313,9 @@ public NearSearchDefinition( _pivot = pivot; } - private protected override BsonDocument RenderArguments(RenderArgs args, - IBsonSerializer fieldSerializer = null) => - new() + private protected override BsonDocument RenderArguments( + RenderArgs args, + IBsonSerializer fieldSerializer) => new() { { "origin", _origin }, { "pivot", _pivot } @@ -329,9 +338,9 @@ public PhraseSearchDefinition( _slop = slop; } - private protected override BsonDocument RenderArguments(RenderArgs args, - IBsonSerializer fieldSerializer = null) => - new() + private protected override BsonDocument RenderArguments( + RenderArgs args, + IBsonSerializer fieldSerializer) => new() { { "query", _query.Render() }, { "slop", _slop, _slop != null } @@ -350,9 +359,9 @@ public QueryStringSearchDefinition(FieldDefinition defaultPath, strin _query = Ensure.IsNotNull(query, nameof(query)); } - private protected override BsonDocument RenderArguments(RenderArgs args, - IBsonSerializer fieldSerializer = null) => - new() + private protected override BsonDocument RenderArguments( + RenderArgs args, + IBsonSerializer fieldSerializer) => new() { { "defaultPath", _defaultPath.Render(args) }, { "query", _query } @@ -373,8 +382,9 @@ public RangeSearchDefinition( _range = range; } - private protected override BsonDocument RenderArguments(RenderArgs args, - IBsonSerializer fieldSerializer = null) + private protected override BsonDocument RenderArguments( + RenderArgs args, + IBsonSerializer fieldSerializer) { var valueSerializer = fieldSerializer switch { @@ -419,9 +429,9 @@ public RegexSearchDefinition( _allowAnalyzedField = allowAnalyzedField; } - private protected override BsonDocument RenderArguments(RenderArgs args, - IBsonSerializer fieldSerializer = null) => - new() + private protected override BsonDocument RenderArguments( + RenderArgs args, + IBsonSerializer fieldSerializer) => new() { { "query", _query.Render() }, { "allowAnalyzedField", _allowAnalyzedField, _allowAnalyzedField }, @@ -438,9 +448,9 @@ public SpanSearchDefinition(SearchSpanDefinition clause) _clause = Ensure.IsNotNull(clause, nameof(clause)); } - private protected override BsonDocument RenderArguments(RenderArgs args, - IBsonSerializer fieldSerializer = null) => - _clause.Render(args); + private protected override BsonDocument RenderArguments( + RenderArgs args, + IBsonSerializer fieldSerializer) => _clause.Render(args); } internal sealed class TextSearchDefinition : OperatorSearchDefinition @@ -463,8 +473,7 @@ public TextSearchDefinition( } private protected override BsonDocument RenderArguments(RenderArgs args, - IBsonSerializer fieldSerializer = null) => - new() + IBsonSerializer fieldSerializer) => new() { { "query", _query.Render() }, { "fuzzy", () => _fuzzy.Render(), _fuzzy != null }, @@ -489,8 +498,7 @@ public WildcardSearchDefinition( } private protected override BsonDocument RenderArguments(RenderArgs args, - IBsonSerializer fieldSerializer = null) => - new() + IBsonSerializer fieldSerializer) => new() { { "query", _query.Render() }, { "allowAnalyzedField", _allowAnalyzedField, _allowAnalyzedField }, diff --git a/src/MongoDB.Driver/Search/SearchDefinition.cs b/src/MongoDB.Driver/Search/SearchDefinition.cs index 4f9bc49e98e..db2b81aff62 100644 --- a/src/MongoDB.Driver/Search/SearchDefinition.cs +++ b/src/MongoDB.Driver/Search/SearchDefinition.cs @@ -159,7 +159,7 @@ public override BsonDocument Render(RenderArgs args) if (_path is null) { - renderedArgs = RenderArguments(args); + renderedArgs = RenderArguments(args, null); } else { @@ -173,6 +173,6 @@ public override BsonDocument Render(RenderArgs args) } private protected virtual BsonDocument RenderArguments(RenderArgs args, - IBsonSerializer fieldSerializer = null) => new(); + IBsonSerializer fieldSerializer) => new(); } } From 484fa7e49bec5b6210e03863ea975af8725921e3 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 11 Feb 2025 09:51:13 +0100 Subject: [PATCH 37/42] Corrections according to PR --- src/MongoDB.Driver/Search/SearchDefinition.cs | 2 +- .../Search/SearchPathDefinition.cs | 18 ++++++++++++++---- .../Search/SearchPathDefinitionBuilder.cs | 4 ++-- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/MongoDB.Driver/Search/SearchDefinition.cs b/src/MongoDB.Driver/Search/SearchDefinition.cs index db2b81aff62..3eac62456fc 100644 --- a/src/MongoDB.Driver/Search/SearchDefinition.cs +++ b/src/MongoDB.Driver/Search/SearchDefinition.cs @@ -163,7 +163,7 @@ public override BsonDocument Render(RenderArgs args) } else { - var (renderedPath, fieldSerializer) = _path.RenderAndGetFieldSerializer(args); + var renderedPath = _path.RenderAndGetFieldSerializer(args, out var fieldSerializer); renderedArgs = RenderArguments(args, fieldSerializer); renderedArgs.Add("path", renderedPath); } diff --git a/src/MongoDB.Driver/Search/SearchPathDefinition.cs b/src/MongoDB.Driver/Search/SearchPathDefinition.cs index fb5ee49e3a4..9e018cc4847 100644 --- a/src/MongoDB.Driver/Search/SearchPathDefinition.cs +++ b/src/MongoDB.Driver/Search/SearchPathDefinition.cs @@ -105,17 +105,27 @@ public static implicit operator SearchPathDefinition(List fie /// The render arguments. /// The rendered field. protected string RenderField(FieldDefinition fieldDefinition, RenderArgs args) - => RenderFieldAndGetFieldSerializer(fieldDefinition, args).renderedPath; + => RenderFieldAndGetFieldSerializer(fieldDefinition, args, out _); - internal virtual (BsonValue, IBsonSerializer) RenderAndGetFieldSerializer(RenderArgs args) => (Render(args), null); + internal virtual BsonValue RenderAndGetFieldSerializer( + RenderArgs args, + out IBsonSerializer fieldSerializer) + { + fieldSerializer = null; + return Render(args); + } - internal (string renderedPath, IBsonSerializer fieldSerializer) RenderFieldAndGetFieldSerializer(FieldDefinition fieldDefinition, RenderArgs args) + internal string RenderFieldAndGetFieldSerializer( + FieldDefinition fieldDefinition, + RenderArgs args, + out IBsonSerializer fieldSerializer) { var renderedField = fieldDefinition.Render(args); var prefix = args.PathRenderArgs.PathPrefix; var renderedString = prefix == null ? renderedField.FieldName : $"{prefix}.{renderedField.FieldName}"; + fieldSerializer = renderedField.FieldSerializer; - return (renderedString, renderedField.FieldSerializer); + return renderedString; } } } diff --git a/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs b/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs index aa4160b336d..6d8851aa129 100644 --- a/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs +++ b/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs @@ -146,8 +146,8 @@ public SingleSearchPathDefinition(FieldDefinition field) public override BsonValue Render(RenderArgs args) => RenderField(_field, args); - internal override (BsonValue, IBsonSerializer) RenderAndGetFieldSerializer(RenderArgs args) - => RenderFieldAndGetFieldSerializer(_field, args); + internal override BsonValue RenderAndGetFieldSerializer(RenderArgs args, out IBsonSerializer fieldSerializer) + => RenderFieldAndGetFieldSerializer(_field, args, out fieldSerializer); } internal sealed class WildcardSearchPathDefinition : SearchPathDefinition From 24503263eab893ebe44bcd7638aac1ffe2263943 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 11 Feb 2025 11:41:18 +0100 Subject: [PATCH 38/42] Naming correction --- src/MongoDB.Driver/Search/SearchPathDefinition.cs | 9 ++++----- src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/MongoDB.Driver/Search/SearchPathDefinition.cs b/src/MongoDB.Driver/Search/SearchPathDefinition.cs index 9e018cc4847..7ef9058f6ef 100644 --- a/src/MongoDB.Driver/Search/SearchPathDefinition.cs +++ b/src/MongoDB.Driver/Search/SearchPathDefinition.cs @@ -105,7 +105,7 @@ public static implicit operator SearchPathDefinition(List fie /// The render arguments. /// The rendered field. protected string RenderField(FieldDefinition fieldDefinition, RenderArgs args) - => RenderFieldAndGetFieldSerializer(fieldDefinition, args, out _); + => RenderField(fieldDefinition, args, out _); internal virtual BsonValue RenderAndGetFieldSerializer( RenderArgs args, @@ -115,17 +115,16 @@ internal virtual BsonValue RenderAndGetFieldSerializer( return Render(args); } - internal string RenderFieldAndGetFieldSerializer( + internal string RenderField( FieldDefinition fieldDefinition, RenderArgs args, out IBsonSerializer fieldSerializer) { var renderedField = fieldDefinition.Render(args); var prefix = args.PathRenderArgs.PathPrefix; - var renderedString = prefix == null ? renderedField.FieldName : $"{prefix}.{renderedField.FieldName}"; - fieldSerializer = renderedField.FieldSerializer; - return renderedString; + fieldSerializer = renderedField.FieldSerializer; + return prefix == null ? renderedField.FieldName : $"{prefix}.{renderedField.FieldName}"; } } } diff --git a/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs b/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs index 6d8851aa129..b72b03dc716 100644 --- a/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs +++ b/src/MongoDB.Driver/Search/SearchPathDefinitionBuilder.cs @@ -147,7 +147,7 @@ public override BsonValue Render(RenderArgs args) => RenderField(_field, args); internal override BsonValue RenderAndGetFieldSerializer(RenderArgs args, out IBsonSerializer fieldSerializer) - => RenderFieldAndGetFieldSerializer(_field, args, out fieldSerializer); + => RenderField(_field, args, out fieldSerializer); } internal sealed class WildcardSearchPathDefinition : SearchPathDefinition From 6e2d349ea66a7e3dfa100e7ded9b85d67b67bf00 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 11 Feb 2025 11:42:16 +0100 Subject: [PATCH 39/42] Added extension method to support old and new behaviour --- .../Search/OperatorSearchDefinitions.cs | 26 +- src/MongoDB.Driver/Search/SearchDefinition.cs | 59 ++++- .../Search/SearchDefinitionBuilderTests.cs | 241 +++++++++++++++--- 3 files changed, 282 insertions(+), 44 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index 01148c02e97..09acde03f8b 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -47,8 +47,7 @@ public AutocompleteSearchDefinition( private protected override BsonDocument RenderArguments( RenderArgs args, - IBsonSerializer fieldSerializer) => - new() + IBsonSerializer fieldSerializer) => new() { { "query", _query.Render() }, { "tokenOrder", _tokenOrder.ToCamelCase(), _tokenOrder != SearchAutocompleteTokenOrder.Any }, @@ -139,6 +138,11 @@ private protected override BsonDocument RenderArguments( RenderArgs args, IBsonSerializer fieldSerializer) { + if (!_useDefaultSerialization) + { + return new BsonDocument("value", ToBsonValue(_value)); + } + var valueSerializer = fieldSerializer switch { null => args.SerializerRegistry.GetSerializer(), @@ -253,6 +257,12 @@ private protected override BsonDocument RenderArguments( RenderArgs args, IBsonSerializer fieldSerializer) { + if (!_useDefaultSerialization) + { + var values = new BsonArray(_values.Select(ToBsonValue)); + return new BsonDocument("value", values); + } + var valueSerializer = fieldSerializer switch { null => new ArraySerializer(args.SerializerRegistry.GetSerializer()), @@ -386,6 +396,18 @@ private protected override BsonDocument RenderArguments( RenderArgs args, IBsonSerializer fieldSerializer) { + if (!_useDefaultSerialization) + { + var min = ToBsonValue(_range.Min); + var max = ToBsonValue(_range.Max); + + return new BsonDocument + { + { _range.IsMinInclusive ? "gte" : "gt", min, min != BsonNull.Value }, + { _range.IsMaxInclusive ? "lte" : "lt", max, max != BsonNull.Value }, + }; + } + var valueSerializer = fieldSerializer switch { null => args.SerializerRegistry.GetSerializer(), diff --git a/src/MongoDB.Driver/Search/SearchDefinition.cs b/src/MongoDB.Driver/Search/SearchDefinition.cs index 3eac62456fc..be405174a19 100644 --- a/src/MongoDB.Driver/Search/SearchDefinition.cs +++ b/src/MongoDB.Driver/Search/SearchDefinition.cs @@ -13,6 +13,7 @@ * limitations under the License. */ +using System; using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Driver.Core.Misc; @@ -55,6 +56,31 @@ public static implicit operator SearchDefinition(string json) => json != null ? new JsonSearchDefinition(json) : null; } + /// + /// Extensions for SearchDefinition + /// + public static class SearchDefinitionExtensions + { + /// + /// Sets the use of default serialization for the specified . + /// When set to true, the default serializers will be used to serialize the values of certain Atlas Search operators, such as "Equals", "In" and "Range". This will become the default behaviour in version 4.0 of the library. + /// If not enabled, then a default conversion will be used. + /// + /// The type of the document. + /// The search definition instance. + /// Whether to use the default serialization or not. + /// The same instance with default serialization enabled. + /// Thrown if is not of a valid type/>. + public static SearchDefinition WithDefaultSerialization(this SearchDefinition searchDefinition, bool useDefaultSerialization) + { + if (searchDefinition is not OperatorSearchDefinition op) + throw new InvalidOperationException("Default serialization cannot be used with the current SearchDefinition type"); + + op.SetUseDefaultSerialization(useDefaultSerialization); + return searchDefinition; + } + } + /// /// A search definition based on a BSON document. /// @@ -134,6 +160,8 @@ private protected enum OperatorType protected readonly SearchPathDefinition _path; protected readonly SearchScoreDefinition _score; + protected bool _useDefaultSerialization = false; + private protected OperatorSearchDefinition(OperatorType operatorType) : this(operatorType, null) { @@ -172,7 +200,36 @@ public override BsonDocument Render(RenderArgs args) return new(_operatorType.ToCamelCase(), renderedArgs); } - private protected virtual BsonDocument RenderArguments(RenderArgs args, + private protected virtual BsonDocument RenderArguments( + RenderArgs args, IBsonSerializer fieldSerializer) => new(); + + internal void SetUseDefaultSerialization(bool useDefaultSerialization) + { + _useDefaultSerialization = useDefaultSerialization; + } + + protected static BsonValue ToBsonValue(T value) => + value switch + { + bool v => (BsonBoolean)v, + sbyte v => (BsonInt32)v, + byte v => (BsonInt32)v, + short v => (BsonInt32)v, + ushort v => (BsonInt32)v, + int v => (BsonInt32)v, + uint v => (BsonInt64)v, + long v => (BsonInt64)v, + float v => (BsonDouble)v, + double v => (BsonDouble)v, + decimal v => (BsonDecimal128)v, + DateTime v => (BsonDateTime)v, + DateTimeOffset v => (BsonDateTime)v.UtcDateTime, + ObjectId v => (BsonObjectId)v, + Guid v => new BsonBinaryData(v, GuidRepresentation.Standard), + string v => (BsonString)v, + null => BsonNull.Value, + _ => throw new InvalidCastException() + }; } } diff --git a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs index cad7ab6981f..da80a502278 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs @@ -288,26 +288,71 @@ public void Equals_should_render_supported_type( var subject = CreateSubject(); var subjectTyped = CreateSubject(); + AssertRendered( + subject.Equals("x", value), + $"{{ equals: {{ path: 'x', value: {valueRendered} }} }}"); + + var scoreBuilder = new SearchScoreDefinitionBuilder(); + AssertRendered( + subject.Equals("x", value, scoreBuilder.Constant(1)), + $"{{ equals: {{ path: 'x', value: {valueRendered}, score: {{ constant: {{ value: 1 }} }} }} }}"); + + AssertRendered( + subjectTyped.Equals(fieldExpression, value), + $"{{ equals: {{ path: '{fieldRendered}', value: {valueRendered} }} }}"); + } + + [Theory] + [MemberData(nameof(EqualsWithDefaultSerializerSupportedTypesTestData))] + public void Equals_with_default_serializer_should_render_supported_type( + T value, + string valueRendered, + Expression> fieldExpression, + string fieldRendered) + { + var subject = CreateSubject(); + var subjectTyped = CreateSubject(); + //When using an untyped query, the default GuidSerializer is used, and that will throw an exception //because the GuidRepresentation is Unspecified. if (typeof(T) != typeof(Guid)) { AssertRendered( - subject.Equals("x", value), + subject.Equals("x", value).WithDefaultSerialization(true), $"{{ equals: {{ path: 'x', value: {valueRendered} }} }}"); var scoreBuilder = new SearchScoreDefinitionBuilder(); AssertRendered( - subject.Equals("x", value, scoreBuilder.Constant(1)), + subject.Equals("x", value, scoreBuilder.Constant(1)).WithDefaultSerialization(true), $"{{ equals: {{ path: 'x', value: {valueRendered}, score: {{ constant: {{ value: 1 }} }} }} }}"); } AssertRendered( - subjectTyped.Equals(fieldExpression, value), + subjectTyped.Equals(fieldExpression, value).WithDefaultSerialization(true), $"{{ equals: {{ path: '{fieldRendered}', value: {valueRendered} }} }}"); } public static object[][] EqualsSupportedTypesTestData => new[] + { + new object[] { true, "true", Exp(p => p.Retired), "ret" }, + new object[] { (sbyte)1, "1", Exp(p => p.Int8), nameof(Person.Int8), }, + new object[] { (byte)1, "1", Exp(p => p.UInt8), nameof(Person.UInt8), }, + new object[] { (short)1, "1", Exp(p => p.Int16), nameof(Person.Int16) }, + new object[] { (ushort)1, "1", Exp(p => p.UInt16), nameof(Person.UInt16) }, + new object[] { (int)1, "1", Exp(p => p.Int32), nameof(Person.Int32) }, + new object[] { (uint)1, "1", Exp(p => p.UInt32), nameof(Person.UInt32) }, + new object[] { long.MaxValue, "NumberLong(\"9223372036854775807\")", Exp(p => p.Int64), nameof(Person.Int64) }, + new object[] { (float)1, "1", Exp(p => p.Float), nameof(Person.Float) }, + new object[] { (double)1, "1", Exp(p => p.Double), nameof(Person.Double) }, + new object[] { DateTime.MinValue, "ISODate(\"0001-01-01T00:00:00Z\")", Exp(p => p.Birthday), "dob" }, + new object[] { DateTimeOffset.MaxValue, "ISODate(\"9999-12-31T23:59:59.999Z\")", Exp(p => p.DateTimeOffset), nameof(Person.DateTimeOffset) }, + new object[] { ObjectId.Empty, "{ $oid: '000000000000000000000000' }", Exp(p => p.Id), "_id" }, + new object[] { Guid.Empty, """{ "$binary" : { "base64" : "AAAAAAAAAAAAAAAAAAAAAA==", "subType" : "04" } }""", Exp(p => p.Guid), nameof(Person.Guid) }, + new object[] { null, "null", Exp(p => p.Name), nameof(Person.Name) }, + new object[] { "Jim", "\"Jim\"", Exp(p => p.FirstName), "fn" } + }; + + public static object[][] EqualsWithDefaultSerializerSupportedTypesTestData => new[] { new object[] { true, "true", Exp(p => p.Retired), "ret" }, new object[] { (sbyte)1, "1", Exp(p => p.Int8), nameof(Person.Int8), }, @@ -328,32 +373,32 @@ public void Equals_should_render_supported_type( }; [Fact] - public void Equals_should_use_correct_serializers_when_using_attributes_and_expression_path() + public void Equals_with_default_serializer_should_use_correct_serializers_when_using_attributes_and_expression_path() { var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); var subjectTyped = CreateSubject(); AssertRendered( - subjectTyped.Equals(t => t.DefaultGuid, testGuid), + subjectTyped.Equals(t => t.DefaultGuid, testGuid).WithDefaultSerialization(true), """{ "equals" : { "value" : { "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }, "path" : "DefaultGuid" } } """); AssertRendered( - subjectTyped.Equals(t => t.StringGuid, testGuid), + subjectTyped.Equals(t => t.StringGuid, testGuid).WithDefaultSerialization(true), """{ "equals" : { "value" : "01020304-0506-0708-090a-0b0c0d0e0f10", "path" : "StringGuid" } }"""); } [Fact] - public void Equals_should_use_correct_serializers_when_using_attributes_and_string_path() + public void Equals_with_default_serializer_should_use_correct_serializers_when_using_attributes_and_string_path() { var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); var subjectTyped = CreateSubject(); AssertRendered( - subjectTyped.Equals("DefaultGuid", testGuid), + subjectTyped.Equals("DefaultGuid", testGuid).WithDefaultSerialization(true), """{ "equals" : { "value" : { "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }, "path" : "DefaultGuid" } } """); AssertRendered( - subjectTyped.Equals("StringGuid", testGuid), + subjectTyped.Equals("StringGuid", testGuid).WithDefaultSerialization(true), """{ "equals" : { "value" : "01020304-0506-0708-090a-0b0c0d0e0f10", "path" : "StringGuid" } }"""); } @@ -366,7 +411,7 @@ public void Equals_should_use_correct_serializers_when_using_serializer_registry var subjectTyped = CreateSubject(); AssertRendered( - subjectTyped.Equals(t => t.UndefinedRepresentationGuid, testGuid), + subjectTyped.Equals(t => t.UndefinedRepresentationGuid, testGuid).WithDefaultSerialization(true), """{ "equals" : { "value" : "01020304-0506-0708-090a-0b0c0d0e0f10", "path" : "UndefinedRepresentationGuid" } }"""); } @@ -537,23 +582,54 @@ public void In(T[] fieldValues, string[] fieldsRendered) $"{{ in: {{ path: 'x', value: [{string.Join(",", fieldsRendered)}] }} }}"); } + [Theory] + [MemberData(nameof(InWithDefaultSerializationTestData))] + public void InWithDefaultSerialization(T[] fieldValues, string[] fieldsRendered) + { + var subject = CreateSubject(); + + AssertRendered( + subject.In("x", fieldValues).WithDefaultSerialization(true), + $"{{ in: {{ path: 'x', value: [{string.Join(",", fieldsRendered)}] }} }}"); + } + public static readonly object[][] InTestData = { - new object[] { new bool[] { true, false }, new[] { "true", "false" } }, - new object[] { new byte[] { 1, 2 }, new[] { "1", "2" } }, - new object[] { new sbyte[] { 1, 2 }, new[] { "1", "2" } }, - new object[] { new short[] { 1, 2 }, new[] { "1", "2" } }, - new object[] { new ushort[] { 1, 2 }, new[] { "1", "2" } }, - new object[] { new int[] { 1, 2 }, new[] { "1", "2" } }, - new object[] { new uint[] { 1, 2 }, new[] { "1", "2" } }, - new object[] { new long[] { long.MaxValue, long.MinValue }, new[] { "NumberLong(\"9223372036854775807\")", "NumberLong(\"-9223372036854775808\")" } }, - new object[] { new float[] { 1.5f, 2.5f }, new[] { "1.5", "2.5" } }, - new object[] { new double[] { 1.5, 2.5 }, new[] { "1.5", "2.5" } }, - new object[] { new decimal[] { 1.5m, 2.5m }, new[] { "NumberDecimal(\"1.5\")", "NumberDecimal(\"2.5\")" } }, - new object[] { new[] { "str1", "str2" }, new[] { "'str1'", "'str2'" } }, - new object[] { new[] { DateTime.MinValue, DateTime.MaxValue }, new[] { "ISODate(\"0001-01-01T00:00:00Z\")", "ISODate(\"9999-12-31T23:59:59.999Z\")" } }, - new object[] { new[] { DateTimeOffset.MinValue, DateTimeOffset.MaxValue }, new[] { """{ "DateTime" : { "$date" : { "$numberLong" : "-62135596800000" } }, "Ticks" : 0, "Offset" : 0 } """, """ { "DateTime" : { "$date" : "9999-12-31T23:59:59.999Z" }, "Ticks" : 3155378975999999999, "Offset" : 0 } """ } }, - new object[] { new[] { ObjectId.Empty, ObjectId.Parse("4d0ce088e447ad08b4721a37") }, new[] { "{ $oid: '000000000000000000000000' }", "{ $oid: '4d0ce088e447ad08b4721a37' }" } }, + new object[] { new bool[] { true, false }, new[] { "true", "false" } }, + new object[] { new byte[] { 1, 2 }, new[] { "1", "2" } }, + new object[] { new sbyte[] { 1, 2 }, new[] { "1", "2" } }, + new object[] { new short[] { 1, 2 }, new[] { "1", "2" } }, + new object[] { new ushort[] { 1, 2 }, new[] { "1", "2" } }, + new object[] { new int[] { 1, 2 }, new[] { "1", "2" } }, + new object[] { new uint[] { 1, 2 }, new[] { "1", "2" } }, + new object[] { new long[] { long.MaxValue, long.MinValue }, new[] { "NumberLong(\"9223372036854775807\")", "NumberLong(\"-9223372036854775808\")" } }, + new object[] { new float[] { 1.5f, 2.5f }, new[] { "1.5", "2.5" } }, + new object[] { new double[] { 1.5, 2.5 }, new[] { "1.5", "2.5" } }, + new object[] { new decimal[] { 1.5m, 2.5m }, new[] { "NumberDecimal(\"1.5\")", "NumberDecimal(\"2.5\")" } }, + new object[] { new[] { "str1", "str2" }, new[] { "'str1'", "'str2'" } }, + new object[] { new[] { DateTime.MinValue, DateTime.MaxValue }, new[] { "ISODate(\"0001-01-01T00:00:00Z\")", "ISODate(\"9999-12-31T23:59:59.999Z\")" } }, + new object[] { new[] { DateTimeOffset.MinValue, DateTimeOffset.MaxValue }, new[] { "ISODate(\"0001-01-01T00:00:00Z\")", "ISODate(\"9999-12-31T23:59:59.999Z\")" } }, + new object[] { new[] { ObjectId.Empty, ObjectId.Parse("4d0ce088e447ad08b4721a37") }, new[] { "{ $oid: '000000000000000000000000' }", "{ $oid: '4d0ce088e447ad08b4721a37' }" } }, + new object[] { new object[] { (byte)1, (short)2, (int)3 }, new[] { "1", "2", "3" } } + }; + + public static readonly object[][] InWithDefaultSerializationTestData = + { + new object[] { new bool[] { true, false }, new[] { "true", "false" } }, + new object[] { new byte[] { 1, 2 }, new[] { "1", "2" } }, + new object[] { new sbyte[] { 1, 2 }, new[] { "1", "2" } }, + new object[] { new short[] { 1, 2 }, new[] { "1", "2" } }, + new object[] { new ushort[] { 1, 2 }, new[] { "1", "2" } }, + new object[] { new int[] { 1, 2 }, new[] { "1", "2" } }, + new object[] { new uint[] { 1, 2 }, new[] { "1", "2" } }, + new object[] { new long[] { long.MaxValue, long.MinValue }, new[] { "NumberLong(\"9223372036854775807\")", "NumberLong(\"-9223372036854775808\")" } }, + new object[] { new float[] { 1.5f, 2.5f }, new[] { "1.5", "2.5" } }, + new object[] { new double[] { 1.5, 2.5 }, new[] { "1.5", "2.5" } }, + new object[] { new decimal[] { 1.5m, 2.5m }, new[] { "NumberDecimal(\"1.5\")", "NumberDecimal(\"2.5\")" } }, + new object[] { new[] { "str1", "str2" }, new[] { "'str1'", "'str2'" } }, + new object[] { new[] { DateTime.MinValue, DateTime.MaxValue }, new[] { "ISODate(\"0001-01-01T00:00:00Z\")", "ISODate(\"9999-12-31T23:59:59.999Z\")" } }, + new object[] { new[] { DateTimeOffset.MinValue, DateTimeOffset.MaxValue }, new[] { """{ "DateTime" : { "$date" : { "$numberLong" : "-62135596800000" } }, "Ticks" : 0, "Offset" : 0 } """, """ { "DateTime" : { "$date" : "9999-12-31T23:59:59.999Z" }, "Ticks" : 3155378975999999999, "Offset" : 0 } """ } }, + new object[] { new[] { ObjectId.Empty, ObjectId.Parse("4d0ce088e447ad08b4721a37") }, new[] { "{ $oid: '000000000000000000000000' }", "{ $oid: '4d0ce088e447ad08b4721a37' }" } }, }; [Theory] @@ -563,6 +639,26 @@ public void In_typed( string[] fieldValuesRendered, Expression> fieldExpression, string fieldNameRendered) + { + var subjectTyped = CreateSubject(); + var fieldValuesArray = $"[{string.Join(",", fieldValuesRendered)}]"; + + AssertRendered( + subjectTyped.In("x", fieldValues), + $"{{ in: {{ path: 'x', value: {fieldValuesArray} }} }}"); + + AssertRendered( + subjectTyped.In(fieldExpression, fieldValues), + $"{{ in: {{path: '{fieldNameRendered}', value: {fieldValuesArray} }} }}"); + } + + [Theory] + [MemberData(nameof(InTypedWithDefaultSerializationTestData))] + public void InWithDefaultSerialization_typed( + T[] fieldValues, + string[] fieldValuesRendered, + Expression> fieldExpression, + string fieldNameRendered) { var subject = CreateSubject(); var fieldValuesArray = $"[{string.Join(",", fieldValuesRendered)}]"; @@ -571,16 +667,37 @@ public void In_typed( if (typeof(T) != typeof(Guid)) { AssertRendered( - subject.In("x", fieldValues), + subject.In("x", fieldValues).WithDefaultSerialization(true), $"{{ in: {{ path: 'x', value: {fieldValuesArray} }} }}"); } AssertRendered( - subject.In(fieldExpression, fieldValues), + subject.In(fieldExpression, fieldValues).WithDefaultSerialization(true), $"{{ in: {{path: '{fieldNameRendered}', value: {fieldValuesArray} }} }}"); } public static readonly object[][] InTypedTestData = + { + new object[] { new bool[] { true, false }, new[] { "true", "false" }, Exp(p => p.Retired), "ret" }, + new object[] { new byte[] { 1, 2 }, new[] { "1", "2" }, Exp(p => p.UInt8), nameof(Person.UInt8) }, + new object[] { new sbyte[] { 1, 2 }, new[] { "1", "2" }, Exp(p => p.Int8), nameof(Person.Int8) }, + new object[] { new short[] { 1, 2 }, new[] { "1", "2" }, Exp(p => p.Int16), nameof(Person.Int16) }, + new object[] { new ushort[] { 1, 2 }, new[] { "1", "2" }, Exp(p => p.UInt16), nameof(Person.UInt16) }, + new object[] { new int[] { 1, 2 }, new[] { "1", "2" }, Exp(p => p.Int32), nameof(Person.Int32) }, + new object[] { new uint[] { 1, 2 }, new[] { "1", "2" }, Exp(p => p.UInt32), nameof(Person.UInt32) }, + new object[] { new long[] { long.MaxValue, long.MinValue }, new[] { "NumberLong(\"9223372036854775807\")", "NumberLong(\"-9223372036854775808\")" }, Exp(p => p.Int64), nameof(Person.Int64) }, + new object[] { new float[] { 1.5f, 2.5f }, new[] { "1.5", "2.5" }, Exp(p => p.Float), nameof(Person.Float) }, + new object[] { new double[] { 1.5, 2.5 }, new[] { "1.5", "2.5" }, Exp(p => p.Double), nameof(Person.Double) }, + new object[] { new decimal[] { 1.5m, 2.5m }, new[] { "NumberDecimal(\"1.5\")", "NumberDecimal(\"2.5\")" }, Exp(p => p.Decimal), nameof(Person.Decimal) }, + new object[] { new[] { "str1", "str2" }, new[] { "'str1'", "'str2'" }, Exp(p => p.FirstName), "fn" }, + new object[] { new[] { DateTime.MinValue, DateTime.MaxValue }, new[] { "ISODate(\"0001-01-01T00:00:00Z\")", "ISODate(\"9999-12-31T23:59:59.999Z\")" }, Exp(p => p.Birthday), "dob" }, + new object[] { new[] { DateTimeOffset.MinValue, DateTimeOffset.MaxValue }, new[] { "ISODate(\"0001-01-01T00:00:00Z\")", "ISODate(\"9999-12-31T23:59:59.999Z\")" }, Exp(p => p.DateTimeOffset), nameof(Person.DateTimeOffset)}, + new object[] { new[] { ObjectId.Empty, ObjectId.Parse("4d0ce088e447ad08b4721a37") }, new[] { "{ $oid: '000000000000000000000000' }", "{ $oid: '4d0ce088e447ad08b4721a37' }" }, Exp(p => p.Id), "_id" }, + new object[] { new[] { Guid.Empty, Guid.Parse("b52af144-bc97-454f-a578-418a64fa95bf") }, new[] { """{ "$binary" : { "base64" : "AAAAAAAAAAAAAAAAAAAAAA==", "subType" : "04" } }""", """{ "$binary" : { "base64" : "tSrxRLyXRU+leEGKZPqVvw==", "subType" : "04" } }""" }, Exp(p => p.Guid), nameof(Person.Guid) }, + new object[] { new object[] { (byte)1, (short)2, (int)3 }, new[] { "1", "2", "3" }, Exp(p => p.Object), nameof(Person.Object) } + }; + + public static readonly object[][] InTypedWithDefaultSerializationTestData = { new object[] { new bool[] { true, false }, new[] { "true", "false" }, Exp(p => p.Retired), "ret" }, new object[] { new byte[] { 1, 2 }, new[] { "1", "2" }, Exp(p => p.UInt8), nameof(Person.UInt8) }, @@ -613,37 +730,37 @@ public void In_should_throw_when_values_are_invalid() } [Fact] - public void In_should_use_correct_serializers_when_using_attributes_and_expression_path() + public void In_with_default_serialization_should_use_correct_serializers_when_using_attributes_and_expression_path() { var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); var subjectTyped = CreateSubject(); AssertRendered( - subjectTyped.In(t => t.DefaultGuid, [testGuid, testGuid]), + subjectTyped.In(t => t.DefaultGuid, [testGuid, testGuid]).WithDefaultSerialization(true), """{ "in" : { "value" : [{ "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }, { "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }], "path" : "DefaultGuid" } } """); AssertRendered( - subjectTyped.In(t => t.StringGuid, [testGuid, testGuid]), + subjectTyped.In(t => t.StringGuid, [testGuid, testGuid]).WithDefaultSerialization(true), """{ "in" : { "value" : ["01020304-0506-0708-090a-0b0c0d0e0f10", "01020304-0506-0708-090a-0b0c0d0e0f10"], "path" : "StringGuid" } }"""); } [Fact] - public void In_should_use_correct_serializers_when_using_attributes_and_string_path() + public void In_with_default_serialization_should_use_correct_serializers_when_using_attributes_and_string_path() { var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); var subjectTyped = CreateSubject(); AssertRendered( - subjectTyped.In("DefaultGuid", [testGuid, testGuid]), + subjectTyped.In("DefaultGuid", [testGuid, testGuid]).WithDefaultSerialization(true), """{ "in" : { "value" : [{ "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }, { "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }], "path" : "DefaultGuid" } } """); AssertRendered( - subjectTyped.In("StringGuid", [testGuid, testGuid]), + subjectTyped.In("StringGuid", [testGuid, testGuid]).WithDefaultSerialization(true), """{ "in" : { "value" : ["01020304-0506-0708-090a-0b0c0d0e0f10", "01020304-0506-0708-090a-0b0c0d0e0f10"], "path" : "StringGuid" } }"""); } [Fact(Skip = "This should only be run manually due to the use of BsonSerializer.RegisterSerializer")] - public void In_should_use_correct_serializers_when_using_serializer_registry() + public void In_with_default_serialization_should_use_correct_serializers_when_using_serializer_registry() { BsonSerializer.RegisterSerializer(new GuidSerializer(BsonType.String)); @@ -651,7 +768,7 @@ public void In_should_use_correct_serializers_when_using_serializer_registry() var subjectTyped = CreateSubject(); AssertRendered( - subjectTyped.In(t => t.UndefinedRepresentationGuid, [testGuid, testGuid]), + subjectTyped.In(t => t.UndefinedRepresentationGuid, [testGuid, testGuid]).WithDefaultSerialization(true), """{ "in" : { "value" : ["01020304-0506-0708-090a-0b0c0d0e0f10", "01020304-0506-0708-090a-0b0c0d0e0f10"], "path" : "UndefinedRepresentationGuid" } }"""); } @@ -993,7 +1110,45 @@ public void Range_should_render_supported_types( $"{{ range: {{ path: '{fieldRendered}', gte: {minRendered}, lt: {maxRendered} }} }}"); } + [Theory] + [MemberData(nameof(RangeWithDefaultSerializationSupportedTypesTestData))] + public void Range_with_default_serialization_should_render_supported_types( + T min, + T max, + string minRendered, + string maxRendered, + Expression> fieldExpression, + string fieldRendered) + where T : struct, IComparable + { + var subject = CreateSubject(); + var subjectTyped = CreateSubject(); + + AssertRendered( + subject.Range("age", SearchRangeBuilder.Gte(min).Lt(max)).WithDefaultSerialization(true), + $"{{ range: {{ path: 'age', gte: {minRendered}, lt: {maxRendered} }} }}"); + + AssertRendered( + subjectTyped.Range(fieldExpression, SearchRangeBuilder.Gte(min).Lt(max)).WithDefaultSerialization(true), + $"{{ range: {{ path: '{fieldRendered}', gte: {minRendered}, lt: {maxRendered} }} }}"); + } + public static object[][] RangeSupportedTypesTestData => new[] + { + new object[] { (sbyte)1, (sbyte)2, "1", "2", Exp(p => p.Int8), nameof(Person.Int8) }, + new object[] { (byte)1, (byte)2, "1", "2", Exp(p => p.UInt8), nameof(Person.UInt8) }, + new object[] { (short)1, (short)2, "1", "2", Exp(p => p.Int16), nameof(Person.Int16) }, + new object[] { (ushort)1, (ushort)2, "1", "2", Exp(p => p.UInt16), nameof(Person.UInt16) }, + new object[] { (int)1, (int)2, "1", "2", Exp(p => p.Int32), nameof(Person.Int32) }, + new object[] { (uint)1, (uint)2, "1", "2", Exp(p => p.UInt32), nameof(Person.UInt32) }, + new object[] { long.MinValue, long.MaxValue, "NumberLong(\"-9223372036854775808\")", "NumberLong(\"9223372036854775807\")", Exp(p => p.Int64), nameof(Person.Int64) }, + new object[] { (float)1, (float)2, "1", "2", Exp(p => p.Float), nameof(Person.Float) }, + new object[] { (double)1, (double)2, "1", "2", Exp(p => p.Double), nameof(Person.Double) }, + new object[] { DateTime.MinValue, DateTime.MaxValue, "ISODate(\"0001-01-01T00:00:00Z\")", "ISODate(\"9999-12-31T23:59:59.999Z\")", Exp(p => p.Birthday), "dob" }, + new object[] { DateTimeOffset.MinValue, DateTimeOffset.MaxValue, "ISODate(\"0001-01-01T00:00:00Z\")", "ISODate(\"9999-12-31T23:59:59.999Z\")", Exp(p => p.DateTimeOffset), nameof(Person.DateTimeOffset) } + }; + + public static object[][] RangeWithDefaultSerializationSupportedTypesTestData => new[] { new object[] { (sbyte)1, (sbyte)2, "1", "2", Exp(p => p.Int8), nameof(Person.Int8) }, new object[] { (byte)1, (byte)2, "1", "2", Exp(p => p.UInt8), nameof(Person.UInt8) }, @@ -1009,32 +1164,36 @@ public void Range_should_render_supported_types( }; [Fact] - public void Range_should_use_correct_serializers_when_using_attributes_and_expression_path() + public void Range_with_default_serialization_should_use_correct_serializers_when_using_attributes_and_expression_path() { var testLong = 23; var subjectTyped = CreateSubject(); AssertRendered( - subjectTyped.Range(t => t.DefaultLong, new SearchRange(testLong, null, false, false )), + subjectTyped.Range(t => t.DefaultLong, new SearchRange(testLong, null, false, false )) + .WithDefaultSerialization(true), """{"range" :{ "gt" : 23, "path" : "DefaultLong" }}"""); AssertRendered( - subjectTyped.Range(t => t.StringLong, new SearchRange(testLong, null, false, false )), + subjectTyped.Range(t => t.StringLong, new SearchRange(testLong, null, false, false )) + .WithDefaultSerialization(true), """{"range":{ "gt" : "23", "path" : "StringLong" }}"""); } [Fact] - public void Range_should_use_correct_serializers_when_using_attributes_and_string_path() + public void Range_with_default_serialization_should_use_correct_serializers_when_using_attributes_and_string_path() { var testLong = 23; var subjectTyped = CreateSubject(); AssertRendered( - subjectTyped.Range("DefaultLong", new SearchRange(testLong, null, false, false )), + subjectTyped.Range("DefaultLong", new SearchRange(testLong, null, false, false )) + .WithDefaultSerialization(true), """{"range" :{ "gt" : 23, "path" : "DefaultLong" }}"""); AssertRendered( - subjectTyped.Range("StringLong", new SearchRange(testLong, null, false, false )), + subjectTyped.Range("StringLong", new SearchRange(testLong, null, false, false )) + .WithDefaultSerialization(true), """{"range":{ "gt" : "23", "path" : "StringLong" }}"""); } From eaa16c4c6857e399c1dac96d18d58acd7ca7634c Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Thu, 27 Feb 2025 15:44:11 +0100 Subject: [PATCH 40/42] Small correction again --- src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index 09acde03f8b..341486cc89e 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -379,13 +379,12 @@ private protected override BsonDocument RenderArguments( } internal sealed class RangeSearchDefinition : OperatorSearchDefinition - where TValue : struct, IComparable { - private readonly SearchRange _range; + private readonly SearchRangeV2 _range; public RangeSearchDefinition( SearchPathDefinition path, - SearchRange range, + SearchRangeV2 range, SearchScoreDefinition score) : base(OperatorType.Range, path, score) { From 8522ef36b9362ba9d0c383d124553fe7978a4f1c Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Thu, 27 Feb 2025 15:45:58 +0100 Subject: [PATCH 41/42] Corrected serializer --- .../Search/OperatorSearchDefinitions.cs | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index 341486cc89e..751556be262 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -397,13 +397,25 @@ private protected override BsonDocument RenderArguments( { if (!_useDefaultSerialization) { - var min = ToBsonValue(_range.Min); - var max = ToBsonValue(_range.Max); + BsonValue min = null, max = null; + bool minInclusive = false, maxInclusive = false; - return new BsonDocument + if (_range.Min != null) { - { _range.IsMinInclusive ? "gte" : "gt", min, min != BsonNull.Value }, - { _range.IsMaxInclusive ? "lte" : "lt", max, max != BsonNull.Value }, + min = ToBsonValue(_range.Min.Value); + minInclusive = _range.Min.Inclusive; + } + + if (_range.Max != null) + { + max = ToBsonValue(_range.Max.Value); + maxInclusive = _range.Max.Inclusive; + } + + return new() + { + { minInclusive ? "gte" : "gt", min, min != null }, + { maxInclusive ? "lte" : "lt", max, max != null } }; } @@ -418,15 +430,15 @@ private protected override BsonDocument RenderArguments( using var bsonWriter = new BsonDocumentWriter(document); var context = BsonSerializationContext.CreateRoot(bsonWriter); bsonWriter.WriteStartDocument(); - if (_range.Min is not null) + if (_range.Min != null) { - bsonWriter.WriteName(_range.IsMinInclusive? "gte" : "gt"); - valueSerializer.Serialize(context, _range.Min); + bsonWriter.WriteName(_range.Min.Inclusive? "gte" : "gt"); + valueSerializer.Serialize(context, _range.Min.Value); } if (_range.Max is not null) { - bsonWriter.WriteName(_range.IsMaxInclusive? "lte" : "lt"); - valueSerializer.Serialize(context, _range.Max); + bsonWriter.WriteName(_range.Max.Inclusive? "lte" : "lt"); + valueSerializer.Serialize(context, _range.Max.Value); } bsonWriter.WriteEndDocument(); From 44a8aa8e83d693166880913f9f26f5e8214177b2 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Thu, 27 Feb 2025 16:01:16 +0100 Subject: [PATCH 42/42] Name correction --- .../Search/OperatorSearchDefinitions.cs | 6 +- src/MongoDB.Driver/Search/SearchDefinition.cs | 16 +-- .../Search/SearchDefinitionBuilderTests.cs | 119 ++++++------------ 3 files changed, 52 insertions(+), 89 deletions(-) diff --git a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs index 751556be262..2c0ca7e8df5 100644 --- a/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs +++ b/src/MongoDB.Driver/Search/OperatorSearchDefinitions.cs @@ -138,7 +138,7 @@ private protected override BsonDocument RenderArguments( RenderArgs args, IBsonSerializer fieldSerializer) { - if (!_useDefaultSerialization) + if (!_useConfiguredSerialization) { return new BsonDocument("value", ToBsonValue(_value)); } @@ -257,7 +257,7 @@ private protected override BsonDocument RenderArguments( RenderArgs args, IBsonSerializer fieldSerializer) { - if (!_useDefaultSerialization) + if (!_useConfiguredSerialization) { var values = new BsonArray(_values.Select(ToBsonValue)); return new BsonDocument("value", values); @@ -395,7 +395,7 @@ private protected override BsonDocument RenderArguments( RenderArgs args, IBsonSerializer fieldSerializer) { - if (!_useDefaultSerialization) + if (!_useConfiguredSerialization) { BsonValue min = null, max = null; bool minInclusive = false, maxInclusive = false; diff --git a/src/MongoDB.Driver/Search/SearchDefinition.cs b/src/MongoDB.Driver/Search/SearchDefinition.cs index be405174a19..0d07d88a825 100644 --- a/src/MongoDB.Driver/Search/SearchDefinition.cs +++ b/src/MongoDB.Driver/Search/SearchDefinition.cs @@ -62,21 +62,21 @@ public static implicit operator SearchDefinition(string json) => public static class SearchDefinitionExtensions { /// - /// Sets the use of default serialization for the specified . - /// When set to true, the default serializers will be used to serialize the values of certain Atlas Search operators, such as "Equals", "In" and "Range". This will become the default behaviour in version 4.0 of the library. + /// Sets the use of the configured serialization for the specified . + /// When set to true, the configured serializers will be used to serialize the values of certain Atlas Search operators, such as "Equals", "In" and "Range". This will become the default behaviour in version 4.0 of the library. /// If not enabled, then a default conversion will be used. /// /// The type of the document. /// The search definition instance. - /// Whether to use the default serialization or not. + /// Whether to use the configured serialization or not. /// The same instance with default serialization enabled. /// Thrown if is not of a valid type/>. - public static SearchDefinition WithDefaultSerialization(this SearchDefinition searchDefinition, bool useDefaultSerialization) + public static SearchDefinition WithConfiguredSerialization(this SearchDefinition searchDefinition, bool useDefaultSerialization) { if (searchDefinition is not OperatorSearchDefinition op) throw new InvalidOperationException("Default serialization cannot be used with the current SearchDefinition type"); - op.SetUseDefaultSerialization(useDefaultSerialization); + op.SetUseConfiguredSerialization(useDefaultSerialization); return searchDefinition; } } @@ -160,7 +160,7 @@ private protected enum OperatorType protected readonly SearchPathDefinition _path; protected readonly SearchScoreDefinition _score; - protected bool _useDefaultSerialization = false; + protected bool _useConfiguredSerialization = false; private protected OperatorSearchDefinition(OperatorType operatorType) : this(operatorType, null) @@ -204,9 +204,9 @@ private protected virtual BsonDocument RenderArguments( RenderArgs args, IBsonSerializer fieldSerializer) => new(); - internal void SetUseDefaultSerialization(bool useDefaultSerialization) + internal void SetUseConfiguredSerialization(bool useDefaultSerialization) { - _useDefaultSerialization = useDefaultSerialization; + _useConfiguredSerialization = useDefaultSerialization; } protected static BsonValue ToBsonValue(T value) => diff --git a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs index da80a502278..ec649f4d435 100644 --- a/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/Search/SearchDefinitionBuilderTests.cs @@ -303,8 +303,8 @@ public void Equals_should_render_supported_type( } [Theory] - [MemberData(nameof(EqualsWithDefaultSerializerSupportedTypesTestData))] - public void Equals_with_default_serializer_should_render_supported_type( + [MemberData(nameof(EqualsWithConfiguredSerializerSupportedTypesTestData))] + public void Equals_with_configured_serializer_should_render_supported_type( T value, string valueRendered, Expression> fieldExpression, @@ -318,17 +318,17 @@ public void Equals_with_default_serializer_should_render_supported_type( if (typeof(T) != typeof(Guid)) { AssertRendered( - subject.Equals("x", value).WithDefaultSerialization(true), + subject.Equals("x", value).WithConfiguredSerialization(true), $"{{ equals: {{ path: 'x', value: {valueRendered} }} }}"); var scoreBuilder = new SearchScoreDefinitionBuilder(); AssertRendered( - subject.Equals("x", value, scoreBuilder.Constant(1)).WithDefaultSerialization(true), + subject.Equals("x", value, scoreBuilder.Constant(1)).WithConfiguredSerialization(true), $"{{ equals: {{ path: 'x', value: {valueRendered}, score: {{ constant: {{ value: 1 }} }} }} }}"); } AssertRendered( - subjectTyped.Equals(fieldExpression, value).WithDefaultSerialization(true), + subjectTyped.Equals(fieldExpression, value).WithConfiguredSerialization(true), $"{{ equals: {{ path: '{fieldRendered}', value: {valueRendered} }} }}"); } @@ -352,7 +352,7 @@ public void Equals_with_default_serializer_should_render_supported_type( new object[] { "Jim", "\"Jim\"", Exp(p => p.FirstName), "fn" } }; - public static object[][] EqualsWithDefaultSerializerSupportedTypesTestData => new[] + public static object[][] EqualsWithConfiguredSerializerSupportedTypesTestData => new[] { new object[] { true, "true", Exp(p => p.Retired), "ret" }, new object[] { (sbyte)1, "1", Exp(p => p.Int8), nameof(Person.Int8), }, @@ -373,32 +373,32 @@ public void Equals_with_default_serializer_should_render_supported_type( }; [Fact] - public void Equals_with_default_serializer_should_use_correct_serializers_when_using_attributes_and_expression_path() + public void Equals_with_configured_serializer_should_use_correct_serializers_when_using_attributes_and_expression_path() { var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); var subjectTyped = CreateSubject(); AssertRendered( - subjectTyped.Equals(t => t.DefaultGuid, testGuid).WithDefaultSerialization(true), + subjectTyped.Equals(t => t.DefaultGuid, testGuid).WithConfiguredSerialization(true), """{ "equals" : { "value" : { "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }, "path" : "DefaultGuid" } } """); AssertRendered( - subjectTyped.Equals(t => t.StringGuid, testGuid).WithDefaultSerialization(true), + subjectTyped.Equals(t => t.StringGuid, testGuid).WithConfiguredSerialization(true), """{ "equals" : { "value" : "01020304-0506-0708-090a-0b0c0d0e0f10", "path" : "StringGuid" } }"""); } [Fact] - public void Equals_with_default_serializer_should_use_correct_serializers_when_using_attributes_and_string_path() + public void Equals_with_configured_serializer_should_use_correct_serializers_when_using_attributes_and_string_path() { var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); var subjectTyped = CreateSubject(); AssertRendered( - subjectTyped.Equals("DefaultGuid", testGuid).WithDefaultSerialization(true), + subjectTyped.Equals("DefaultGuid", testGuid).WithConfiguredSerialization(true), """{ "equals" : { "value" : { "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }, "path" : "DefaultGuid" } } """); AssertRendered( - subjectTyped.Equals("StringGuid", testGuid).WithDefaultSerialization(true), + subjectTyped.Equals("StringGuid", testGuid).WithConfiguredSerialization(true), """{ "equals" : { "value" : "01020304-0506-0708-090a-0b0c0d0e0f10", "path" : "StringGuid" } }"""); } @@ -411,7 +411,7 @@ public void Equals_should_use_correct_serializers_when_using_serializer_registry var subjectTyped = CreateSubject(); AssertRendered( - subjectTyped.Equals(t => t.UndefinedRepresentationGuid, testGuid).WithDefaultSerialization(true), + subjectTyped.Equals(t => t.UndefinedRepresentationGuid, testGuid).WithConfiguredSerialization(true), """{ "equals" : { "value" : "01020304-0506-0708-090a-0b0c0d0e0f10", "path" : "UndefinedRepresentationGuid" } }"""); } @@ -583,13 +583,13 @@ public void In(T[] fieldValues, string[] fieldsRendered) } [Theory] - [MemberData(nameof(InWithDefaultSerializationTestData))] - public void InWithDefaultSerialization(T[] fieldValues, string[] fieldsRendered) + [MemberData(nameof(InWithConfiguredSerializationTestData))] + public void InWithConfiguredSerialization(T[] fieldValues, string[] fieldsRendered) { var subject = CreateSubject(); AssertRendered( - subject.In("x", fieldValues).WithDefaultSerialization(true), + subject.In("x", fieldValues).WithConfiguredSerialization(true), $"{{ in: {{ path: 'x', value: [{string.Join(",", fieldsRendered)}] }} }}"); } @@ -613,7 +613,7 @@ public void InWithDefaultSerialization(T[] fieldValues, string[] fieldsRender new object[] { new object[] { (byte)1, (short)2, (int)3 }, new[] { "1", "2", "3" } } }; - public static readonly object[][] InWithDefaultSerializationTestData = + public static readonly object[][] InWithConfiguredSerializationTestData = { new object[] { new bool[] { true, false }, new[] { "true", "false" } }, new object[] { new byte[] { 1, 2 }, new[] { "1", "2" } }, @@ -653,8 +653,8 @@ public void In_typed( } [Theory] - [MemberData(nameof(InTypedWithDefaultSerializationTestData))] - public void InWithDefaultSerialization_typed( + [MemberData(nameof(InTypedWithConfiguredSerializationTestData))] + public void InWithConfiguredSerialization_typed( T[] fieldValues, string[] fieldValuesRendered, Expression> fieldExpression, @@ -667,12 +667,12 @@ public void InWithDefaultSerialization_typed( if (typeof(T) != typeof(Guid)) { AssertRendered( - subject.In("x", fieldValues).WithDefaultSerialization(true), + subject.In("x", fieldValues).WithConfiguredSerialization(true), $"{{ in: {{ path: 'x', value: {fieldValuesArray} }} }}"); } AssertRendered( - subject.In(fieldExpression, fieldValues).WithDefaultSerialization(true), + subject.In(fieldExpression, fieldValues).WithConfiguredSerialization(true), $"{{ in: {{path: '{fieldNameRendered}', value: {fieldValuesArray} }} }}"); } @@ -697,7 +697,7 @@ public void InWithDefaultSerialization_typed( new object[] { new object[] { (byte)1, (short)2, (int)3 }, new[] { "1", "2", "3" }, Exp(p => p.Object), nameof(Person.Object) } }; - public static readonly object[][] InTypedWithDefaultSerializationTestData = + public static readonly object[][] InTypedWithConfiguredSerializationTestData = { new object[] { new bool[] { true, false }, new[] { "true", "false" }, Exp(p => p.Retired), "ret" }, new object[] { new byte[] { 1, 2 }, new[] { "1", "2" }, Exp(p => p.UInt8), nameof(Person.UInt8) }, @@ -730,37 +730,37 @@ public void In_should_throw_when_values_are_invalid() } [Fact] - public void In_with_default_serialization_should_use_correct_serializers_when_using_attributes_and_expression_path() + public void In_with_configured_serialization_should_use_correct_serializers_when_using_attributes_and_expression_path() { var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); var subjectTyped = CreateSubject(); AssertRendered( - subjectTyped.In(t => t.DefaultGuid, [testGuid, testGuid]).WithDefaultSerialization(true), + subjectTyped.In(t => t.DefaultGuid, [testGuid, testGuid]).WithConfiguredSerialization(true), """{ "in" : { "value" : [{ "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }, { "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }], "path" : "DefaultGuid" } } """); AssertRendered( - subjectTyped.In(t => t.StringGuid, [testGuid, testGuid]).WithDefaultSerialization(true), + subjectTyped.In(t => t.StringGuid, [testGuid, testGuid]).WithConfiguredSerialization(true), """{ "in" : { "value" : ["01020304-0506-0708-090a-0b0c0d0e0f10", "01020304-0506-0708-090a-0b0c0d0e0f10"], "path" : "StringGuid" } }"""); } [Fact] - public void In_with_default_serialization_should_use_correct_serializers_when_using_attributes_and_string_path() + public void In_with_configured_serialization_should_use_correct_serializers_when_using_attributes_and_string_path() { var testGuid = Guid.Parse("01020304-0506-0708-090a-0b0c0d0e0f10"); var subjectTyped = CreateSubject(); AssertRendered( - subjectTyped.In("DefaultGuid", [testGuid, testGuid]).WithDefaultSerialization(true), + subjectTyped.In("DefaultGuid", [testGuid, testGuid]).WithConfiguredSerialization(true), """{ "in" : { "value" : [{ "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }, { "$binary" : { "base64" : "AQIDBAUGBwgJCgsMDQ4PEA==", "subType" : "04" } }], "path" : "DefaultGuid" } } """); AssertRendered( - subjectTyped.In("StringGuid", [testGuid, testGuid]).WithDefaultSerialization(true), + subjectTyped.In("StringGuid", [testGuid, testGuid]).WithConfiguredSerialization(true), """{ "in" : { "value" : ["01020304-0506-0708-090a-0b0c0d0e0f10", "01020304-0506-0708-090a-0b0c0d0e0f10"], "path" : "StringGuid" } }"""); } [Fact(Skip = "This should only be run manually due to the use of BsonSerializer.RegisterSerializer")] - public void In_with_default_serialization_should_use_correct_serializers_when_using_serializer_registry() + public void In_with_configured_serialization_should_use_correct_serializers_when_using_serializer_registry() { BsonSerializer.RegisterSerializer(new GuidSerializer(BsonType.String)); @@ -768,7 +768,7 @@ public void In_with_default_serialization_should_use_correct_serializers_when_us var subjectTyped = CreateSubject(); AssertRendered( - subjectTyped.In(t => t.UndefinedRepresentationGuid, [testGuid, testGuid]).WithDefaultSerialization(true), + subjectTyped.In(t => t.UndefinedRepresentationGuid, [testGuid, testGuid]).WithConfiguredSerialization(true), """{ "in" : { "value" : ["01020304-0506-0708-090a-0b0c0d0e0f10", "01020304-0506-0708-090a-0b0c0d0e0f10"], "path" : "UndefinedRepresentationGuid" } }"""); } @@ -1033,30 +1033,6 @@ public void QueryString_typed() "{ queryString: { defaultPath: 'hobbies', query: 'foo' } }"); } - [Fact] - public void RangeDateTime() - { - var subject = CreateSubject(); - - AssertRendered( - subject.Range( - p => p.Birthday, - SearchRangeBuilder - .Gte(new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc)) - .Lte(new DateTime(2009, 12, 31, 0, 0, 0, DateTimeKind.Utc))), - "{ range: { path: 'dob', gte: { $date: '2000-01-01T00:00:00Z' }, lte: { $date: '2009-12-31T00:00:00Z' } } }"); - } - - [Fact] - public void RangeDouble() - { - var subject = CreateSubject(); - - AssertRendered( - subject.Range(p => p.Age, SearchRangeBuilder.Gt(1.5).Lt(2.5)), - "{ range: { path: 'age', gt: 1.5, lt: 2.5 } }"); - } - [Theory] [InlineData(1, null, false, false, "gt: 1")] [InlineData(1, null, true, false, "gte: 1")] @@ -1074,19 +1050,6 @@ public void Range_should_render_correct_operator(int? min, int? max, bool minInc $"{{ range: {{ path: 'x', {rangeRendered} }} }}"); } - [Fact] - public void RangeInt32_typed() - { - var subject = CreateSubject(); - - AssertRendered( - subject.Range(x => x.Age, SearchRangeBuilder.Gte(18).Lt(65)), - "{ range: { path: 'age', gte: 18, lt: 65 } }"); - AssertRendered( - subject.Range("Age", SearchRangeBuilder.Gte(18).Lt(65)), - "{ range: { path: 'age', gte: 18, lt: 65 } }"); - } - [Theory] [MemberData(nameof(RangeSupportedTypesTestData))] public void Range_should_render_supported_types( @@ -1111,8 +1074,8 @@ public void Range_should_render_supported_types( } [Theory] - [MemberData(nameof(RangeWithDefaultSerializationSupportedTypesTestData))] - public void Range_with_default_serialization_should_render_supported_types( + [MemberData(nameof(RangeWithConfiguredSerializationSupportedTypesTestData))] + public void Range_with_configured_serialization_should_render_supported_types( T min, T max, string minRendered, @@ -1125,11 +1088,11 @@ public void Range_with_default_serialization_should_render_supported_types( var subjectTyped = CreateSubject(); AssertRendered( - subject.Range("age", SearchRangeBuilder.Gte(min).Lt(max)).WithDefaultSerialization(true), + subject.Range("age", SearchRangeBuilder.Gte(min).Lt(max)).WithConfiguredSerialization(true), $"{{ range: {{ path: 'age', gte: {minRendered}, lt: {maxRendered} }} }}"); AssertRendered( - subjectTyped.Range(fieldExpression, SearchRangeBuilder.Gte(min).Lt(max)).WithDefaultSerialization(true), + subjectTyped.Range(fieldExpression, SearchRangeBuilder.Gte(min).Lt(max)).WithConfiguredSerialization(true), $"{{ range: {{ path: '{fieldRendered}', gte: {minRendered}, lt: {maxRendered} }} }}"); } @@ -1148,7 +1111,7 @@ public void Range_with_default_serialization_should_render_supported_types( new object[] { DateTimeOffset.MinValue, DateTimeOffset.MaxValue, "ISODate(\"0001-01-01T00:00:00Z\")", "ISODate(\"9999-12-31T23:59:59.999Z\")", Exp(p => p.DateTimeOffset), nameof(Person.DateTimeOffset) } }; - public static object[][] RangeWithDefaultSerializationSupportedTypesTestData => new[] + public static object[][] RangeWithConfiguredSerializationSupportedTypesTestData => new[] { new object[] { (sbyte)1, (sbyte)2, "1", "2", Exp(p => p.Int8), nameof(Person.Int8) }, new object[] { (byte)1, (byte)2, "1", "2", Exp(p => p.UInt8), nameof(Person.UInt8) }, @@ -1164,36 +1127,36 @@ public void Range_with_default_serialization_should_render_supported_types( }; [Fact] - public void Range_with_default_serialization_should_use_correct_serializers_when_using_attributes_and_expression_path() + public void Range_with_configured_serialization_should_use_correct_serializers_when_using_attributes_and_expression_path() { var testLong = 23; var subjectTyped = CreateSubject(); AssertRendered( subjectTyped.Range(t => t.DefaultLong, new SearchRange(testLong, null, false, false )) - .WithDefaultSerialization(true), + .WithConfiguredSerialization(true), """{"range" :{ "gt" : 23, "path" : "DefaultLong" }}"""); AssertRendered( subjectTyped.Range(t => t.StringLong, new SearchRange(testLong, null, false, false )) - .WithDefaultSerialization(true), + .WithConfiguredSerialization(true), """{"range":{ "gt" : "23", "path" : "StringLong" }}"""); } [Fact] - public void Range_with_default_serialization_should_use_correct_serializers_when_using_attributes_and_string_path() + public void Range_with_configured_serialization_should_use_correct_serializers_when_using_attributes_and_string_path() { var testLong = 23; var subjectTyped = CreateSubject(); AssertRendered( subjectTyped.Range("DefaultLong", new SearchRange(testLong, null, false, false )) - .WithDefaultSerialization(true), + .WithConfiguredSerialization(true), """{"range" :{ "gt" : 23, "path" : "DefaultLong" }}"""); AssertRendered( subjectTyped.Range("StringLong", new SearchRange(testLong, null, false, false )) - .WithDefaultSerialization(true), + .WithConfiguredSerialization(true), """{"range":{ "gt" : "23", "path" : "StringLong" }}"""); }