Skip to content

Commit

Permalink
CSHARP-4449: Implement Find projections in LINQ3.
Browse files Browse the repository at this point in the history
  • Loading branch information
rstam committed Jan 12, 2023
1 parent 396830c commit c0c521e
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 25 deletions.
6 changes: 6 additions & 0 deletions src/MongoDB.Driver.Core/Core/Misc/Feature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public class Feature
private static readonly Feature __findAllowDiskUse = new Feature("FindAllowDiskUse", WireVersion.Server44);
private static readonly Feature __findAndModifyWriteConcern = new Feature("FindAndModifyWriteConcern", WireVersion.Server32);
private static readonly Feature __findCommand = new Feature("FindCommand", WireVersion.Server32);
private static readonly Feature __findProjectionExpressions = new Feature("FindProjectionExpressions", WireVersion.Server44);
private static readonly Feature __geoNearCommand = new Feature("GeoNearCommand", WireVersion.Zero, WireVersion.Server42);
private static readonly Feature __getField = new Feature("GetField", WireVersion.Server50);
private static readonly Feature __getMoreComment = new Feature("GetMoreComment", WireVersion.Server44);
Expand Down Expand Up @@ -409,6 +410,11 @@ public class Feature
[Obsolete("This property will be removed in a later release.")]
public static Feature FindCommand => __findCommand;

/// <summary>
/// Gets the find projection expressions feature.
/// </summary>
public static Feature FindProjectionExpressions => __findProjectionExpressions;

/// <summary>
/// Gets the geoNear command feature.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,7 @@ internal override RenderedProjectionDefinition<TProjection> TranslateExpressionT
IBsonSerializer<TSource> sourceSerializer,
IBsonSerializerRegistry serializerRegistry)
{
// TODO: implement using LINQ3 instead of falling back to LINQ2
return LinqProviderAdapter.V2.TranslateExpressionToFindProjection(expression, sourceSerializer, serializerRegistry);
return TranslateExpressionToProjection(expression, sourceSerializer, serializerRegistry, translationOptions: null);
}

internal override RenderedProjectionDefinition<TOutput> TranslateExpressionToGroupProjection<TInput, TKey, TOutput>(
Expand Down
25 changes: 19 additions & 6 deletions tests/MongoDB.Driver.Tests/FindFluentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
using MongoDB.Driver.Linq;
using Moq;
using Xunit;
using MongoDB.Driver.Core.TestHelpers.XunitExtensions;
using MongoDB.Driver.Core.Misc;

namespace MongoDB.Driver.Tests
{
Expand Down Expand Up @@ -303,10 +305,17 @@ public void ToCursor_should_call_collection_Find_with_expected_arguments(
}
}

[Fact]
public void ToString_should_return_the_correct_string()
[Theory]
[ParameterAttributeData]
public void ToString_should_return_the_correct_string(
[Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider)
{
var subject = CreateSubject();
if (linqProvider == LinqProvider.V3)
{
RequireServer.Check().Supports(Feature.FindProjectionExpressions);
}

var subject = CreateSubject(linqProvider: linqProvider);
subject.Filter = new BsonDocument("Age", 20);
subject.Options.Collation = new Collation("en_US");
subject.Options.Comment = "awesome";
Expand All @@ -333,8 +342,12 @@ public void ToString_should_return_the_correct_string()

var str = find.ToString();

var expectedProjection = linqProvider == LinqProvider.V2 ?
"{ \"FirstName\" : 1, \"LastName\" : 1, \"_id\" : 0 }" :
"{ \"_v\" : { \"$concat\" : [\"$FirstName\", \" \", \"$LastName\"] }, \"_id\" : 0 }";

str.Should().Be(
"find({ \"Age\" : 20 }, { \"FirstName\" : 1, \"LastName\" : 1, \"_id\" : 0 })" +
"find({ \"Age\" : 20 }, " + expectedProjection + ")" +
".collation({ \"locale\" : \"en_US\" })" +
".sort({ \"LastName\" : 1, \"FirstName\" : -1 })" +
".skip(2)" +
Expand All @@ -356,9 +369,9 @@ private IClientSessionHandle CreateSession(bool usingSession)
return usingSession ? Mock.Of<IClientSessionHandle>() : null;
}

private IFindFluent<Person, Person> CreateSubject(IClientSessionHandle session = null, FilterDefinition<Person> filter = null, FindOptions<Person, Person> options = null)
private IFindFluent<Person, Person> CreateSubject(IClientSessionHandle session = null, FilterDefinition<Person> filter = null, FindOptions<Person, Person> options = null, LinqProvider linqProvider = LinqProvider.V3)
{
var clientSettings = new MongoClientSettings { LinqProvider = LinqProvider.V2 };
var clientSettings = new MongoClientSettings { LinqProvider = linqProvider };
var mockClient = new Mock<IMongoClient>();
mockClient.SetupGet(c => c.Settings).Returns(clientSettings);

Expand Down
52 changes: 38 additions & 14 deletions tests/MongoDB.Driver.Tests/IFindFluentExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@
using FluentAssertions;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver.Core.Misc;
using MongoDB.Driver.Core.TestHelpers.XunitExtensions;
using MongoDB.Driver.Linq;
using MongoDB.Driver.Tests.Linq.Linq3ImplementationTests;
using MongoDB.TestHelpers.XunitExtensions;
using Moq;
using Xunit;

namespace MongoDB.Driver.Tests
{
public class IFindFluentExtensionsTests
public class IFindFluentExtensionsTests : Linq3IntegrationTest
{
// public methods
[Theory]
Expand Down Expand Up @@ -228,15 +232,27 @@ public void Project_should_generate_the_correct_fields_when_a_string_is_used()
AssertProjection(subject, expectedProjection);
}

[Fact]
public void Project_should_generate_the_correct_fields_and_assign_the_correct_result_serializer()
[Theory]
[ParameterAttributeData]
public void Project_should_generate_the_correct_fields_and_assign_the_correct_result_serializer(
[Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider)
{
var subject = CreateSubject()
if (linqProvider == LinqProvider.V3)
{
RequireServer.Check().Supports(Feature.FindProjectionExpressions);
}

var subject = CreateSubject(linqProvider)
.Project(x => x.FirstName + " " + x.LastName);

var expectedProjection = BsonDocument.Parse("{FirstName: 1, LastName: 1, _id: 0}");
var expectedProjection = linqProvider == LinqProvider.V2 ?
BsonDocument.Parse("{ FirstName : 1, LastName : 1, _id : 0}") :
BsonDocument.Parse("{ _v : { $concat : ['$FirstName', ' ', '$LastName'] }, _id : 0 }");

AssertProjection(subject, expectedProjection);
AssertProjection(subject, expectedProjection, linqProvider);

var results = subject.ToList();
results.Should().Equal("John Doe");
}

[Theory]
Expand Down Expand Up @@ -435,23 +451,31 @@ public void SortByDescending_ThenByDescending_should_generate_the_correct_sort()
AssertSort(subject, expectedSort);
}

private static void AssertProjection<TResult>(IFindFluent<Person, TResult> subject, BsonDocument expectedProjection)
private static void AssertProjection<TResult>(IFindFluent<Person, TResult> subject, BsonDocument expectedProjection, LinqProvider linqProvider = LinqProvider.V3)
{
Assert.Equal(expectedProjection, subject.Options.Projection.Render(BsonSerializer.SerializerRegistry.GetSerializer<Person>(), BsonSerializer.SerializerRegistry).Document);
Assert.Equal(expectedProjection, subject.Options.Projection.Render(BsonSerializer.SerializerRegistry.GetSerializer<Person>(), BsonSerializer.SerializerRegistry, linqProvider).Document);
}

private static void AssertSort(IFindFluent<Person, Person> subject, BsonDocument expectedSort)
{
Assert.Equal(expectedSort, subject.Options.Sort.Render(BsonSerializer.SerializerRegistry.GetSerializer<Person>(), BsonSerializer.SerializerRegistry));
}

private IFindFluent<Person, Person> CreateSubject()
private IMongoCollection<Person> CreateCollection(LinqProvider linqProvider = LinqProvider.V3)
{
var collection = GetCollection<Person>(linqProvider: linqProvider);

CreateCollection(
collection,
new Person { FirstName = "John", LastName = "Doe", Age = 21 });

return collection;
}

private IFindFluent<Person, Person> CreateSubject(LinqProvider linqProvider = LinqProvider.V3)
{
var settings = new MongoCollectionSettings();
var mockCollection = new Mock<IMongoCollection<Person>>();
mockCollection.SetupGet(c => c.Settings).Returns(settings);
var options = new FindOptions<Person, Person>();
return new FindFluent<Person, Person>(session: null, collection: mockCollection.Object, filter: new BsonDocument(), options: options);
var collection = CreateCollection(linqProvider);
return collection.Find("{}");
}

public class Person
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,7 @@ public void TranslateExpressionToFindProjection_should_return_expected_result()

var result = subject.TranslateExpressionToFindProjection(expression, documentSerializer, serializerRegistry);

var expectedResult = LinqProviderAdapter.V2.TranslateExpressionToFindProjection(expression, documentSerializer, serializerRegistry);
expectedResult.Document.Should().Be("{ X : 1, _id : 0 }");
result.Document.Should().Be(expectedResult.Document);
result.Document.Should().Be("{ _v : '$X', _id : 0 }");
result.ProjectionSerializer.ValueType.Should().Be(typeof(int));
}

Expand Down

0 comments on commit c0c521e

Please sign in to comment.