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