Skip to content

Commit 1ef7dd3

Browse files
sanych-sunrstam
authored andcommitted
CSHARP-4656 Simplify A : "$A" to A : 1 only on find (#1087)
1 parent 5a973c2 commit 1ef7dd3

22 files changed

+169
-55
lines changed

src/MongoDB.Driver/FindFluent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ public override string ToString()
197197

198198
if (_options.Projection != null)
199199
{
200-
var renderedProjection = Render(_options.Projection.Render);
200+
var renderedProjection = Render(_options.Projection.RenderForFind);
201201
if (renderedProjection.Document != null)
202202
{
203203
sb.Append(", " + renderedProjection.Document.ToString());

src/MongoDB.Driver/IFindFluentExtensions.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@
1414
*/
1515

1616
using System;
17-
using System.Linq;
1817
using System.Linq.Expressions;
1918
using System.Threading;
2019
using System.Threading.Tasks;
2120
using MongoDB.Bson;
22-
using MongoDB.Bson.Serialization;
2321
using MongoDB.Driver.Core.Misc;
2422

2523
namespace MongoDB.Driver
@@ -59,7 +57,7 @@ public static IFindFluent<TDocument, TNewProjection> Project<TDocument, TProject
5957
Ensure.IsNotNull(find, nameof(find));
6058
Ensure.IsNotNull(projection, nameof(projection));
6159

62-
return find.Project<TNewProjection>(new FindExpressionProjectionDefinition<TDocument, TNewProjection>(projection));
60+
return find.Project<TNewProjection>(new ExpressionProjectionDefinition<TDocument, TNewProjection>(projection, null));
6361
}
6462

6563
/// <summary>
@@ -75,7 +73,7 @@ public static IOrderedFindFluent<TDocument, TProjection> SortBy<TDocument, TProj
7573
Ensure.IsNotNull(find, nameof(find));
7674
Ensure.IsNotNull(field, nameof(field));
7775

78-
// We require an implementation of IFindFluent<TDocument, TProjection>
76+
// We require an implementation of IFindFluent<TDocument, TProjection>
7977
// to also implement IOrderedFindFluent<TDocument, TProjection>
8078
return (IOrderedFindFluent<TDocument, TProjection>)find.Sort(
8179
new DirectionalSortDefinition<TDocument>(new ExpressionFieldDefinition<TDocument>(field), SortDirection.Ascending));
@@ -94,7 +92,7 @@ public static IOrderedFindFluent<TDocument, TProjection> SortByDescending<TDocum
9492
Ensure.IsNotNull(find, nameof(find));
9593
Ensure.IsNotNull(field, nameof(field));
9694

97-
// We require an implementation of IFindFluent<TDocument, TProjection>
95+
// We require an implementation of IFindFluent<TDocument, TProjection>
9896
// to also implement IOrderedFindFluent<TDocument, TProjection>
9997
return (IOrderedFindFluent<TDocument, TProjection>)find.Sort(
10098
new DirectionalSortDefinition<TDocument>(new ExpressionFieldDefinition<TDocument>(field), SortDirection.Descending));
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
17+
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Stages;
18+
19+
namespace MongoDB.Driver.Linq.Linq3Implementation.Ast.Optimizers
20+
{
21+
internal class AstFindProjectionSimplifier : AstSimplifier
22+
{
23+
public override AstNode VisitProjectStageSetFieldSpecification(AstProjectStageSetFieldSpecification node)
24+
{
25+
node = (AstProjectStageSetFieldSpecification)base.VisitProjectStageSetFieldSpecification(node);
26+
27+
// { path : '$path' } => { path : 1 }
28+
if (node.Value is AstFieldPathExpression fieldPathExpression &&
29+
fieldPathExpression.Path == $"${node.Path}")
30+
{
31+
return AstProject.Include(node.Path);
32+
}
33+
34+
return node;
35+
}
36+
}
37+
}

src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Optimizers/AstSimplifier.cs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -307,20 +307,6 @@ static AstExpression UltimateGetFieldInput(AstGetFieldExpression getField)
307307
}
308308
}
309309

310-
public override AstNode VisitProjectStageSetFieldSpecification(AstProjectStageSetFieldSpecification node)
311-
{
312-
node = (AstProjectStageSetFieldSpecification)base.VisitProjectStageSetFieldSpecification(node);
313-
314-
// { path : '$path' } => { path : 1 }
315-
if (node.Value is AstFieldPathExpression fieldPathExpression &&
316-
fieldPathExpression.Path == $"${node.Path}")
317-
{
318-
return AstProject.Include(node.Path);
319-
}
320-
321-
return node;
322-
}
323-
324310
public override AstNode VisitUnaryExpression(AstUnaryExpression node)
325311
{
326312
// { $first : <arg> } => { $arrayElemAt : [<arg>, 0] } (or -1 for $last)

src/MongoDB.Driver/Linq/Linq3Implementation/LinqProviderAdapterV3.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,7 @@ internal override RenderedProjectionDefinition<TProjection> TranslateExpressionT
139139
Expression<Func<TSource, TProjection>> expression,
140140
IBsonSerializer<TSource> sourceSerializer,
141141
IBsonSerializerRegistry serializerRegistry)
142-
{
143-
return TranslateExpressionToProjection(expression, sourceSerializer, serializerRegistry, translationOptions: null);
144-
}
142+
=> TranslateExpressionToProjectionInternal(expression, sourceSerializer, new AstFindProjectionSimplifier());
145143

146144
internal override RenderedProjectionDefinition<TOutput> TranslateExpressionToGroupProjection<TInput, TKey, TOutput>(
147145
Expression<Func<TInput, TKey>> idExpression,
@@ -158,12 +156,18 @@ internal override RenderedProjectionDefinition<TOutput> TranslateExpressionToPro
158156
IBsonSerializer<TInput> inputSerializer,
159157
IBsonSerializerRegistry serializerRegistry,
160158
ExpressionTranslationOptions translationOptions)
159+
=> TranslateExpressionToProjectionInternal(expression, inputSerializer, new AstSimplifier());
160+
161+
private RenderedProjectionDefinition<TOutput> TranslateExpressionToProjectionInternal<TInput, TOutput>(
162+
Expression<Func<TInput, TOutput>> expression,
163+
IBsonSerializer<TInput> inputSerializer,
164+
AstSimplifier simplifier)
161165
{
162166
expression = (Expression<Func<TInput, TOutput>>)PartialEvaluator.EvaluatePartially(expression);
163167
var context = TranslationContext.Create(expression, inputSerializer);
164168
var translation = ExpressionToAggregationExpressionTranslator.TranslateLambdaBody(context, expression, inputSerializer, asRoot: true);
165169
var (projectStage, projectionSerializer) = ProjectionHelper.CreateProjectStage(translation);
166-
var simplifiedProjectStage = AstSimplifier.Simplify(projectStage);
170+
var simplifiedProjectStage = simplifier.Visit(projectStage);
167171
var renderedProjection = simplifiedProjectStage.Render().AsBsonDocument["$project"].AsBsonDocument;
168172

169173
return new RenderedProjectionDefinition<TOutput>(renderedProjection, (IBsonSerializer<TOutput>)projectionSerializer);

src/MongoDB.Driver/MongoCollectionImpl.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1059,7 +1059,7 @@ private FindOneAndUpdateOperation<TProjection> CreateFindOneAndUpdateOperation<T
10591059
private FindOperation<TProjection> CreateFindOperation<TProjection>(FilterDefinition<TDocument> filter, FindOptions<TDocument, TProjection> options)
10601060
{
10611061
var projection = options.Projection ?? new ClientSideDeserializationProjectionDefinition<TDocument, TProjection>();
1062-
var renderedProjection = projection.Render(_documentSerializer, _settings.SerializerRegistry, _linqProvider);
1062+
var renderedProjection = projection.RenderForFind(_documentSerializer, _settings.SerializerRegistry, _linqProvider);
10631063

10641064
return new FindOperation<TProjection>(
10651065
_collectionNamespace,

src/MongoDB.Driver/PipelineStageDefinitionBuilder.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1305,7 +1305,7 @@ public static PipelineStageDefinition<TInput, TOutput> Project<TInput, TOutput>(
13051305
ExpressionTranslationOptions translationOptions = null)
13061306
{
13071307
Ensure.IsNotNull(projection, nameof(projection));
1308-
return Project(new ProjectExpressionProjection<TInput, TOutput>(projection, translationOptions));
1308+
return Project(new ExpressionProjectionDefinition<TInput, TOutput>(projection, translationOptions));
13091309
}
13101310

13111311
/// <summary>
@@ -1905,6 +1905,11 @@ public override RenderedProjectionDefinition<TOutput> Render(IBsonSerializer<TIn
19051905

19061906
return linqProvider.GetAdapter().TranslateExpressionToBucketOutputProjection(_valueExpression, _outputExpression, documentSerializer, serializerRegistry, _translationOptions);
19071907
}
1908+
1909+
internal override RenderedProjectionDefinition<TOutput> RenderForFind(IBsonSerializer<TInput> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
1910+
{
1911+
throw new InvalidOperationException();
1912+
}
19081913
}
19091914

19101915
internal sealed class GroupExpressionProjection<TInput, TKey, TOutput> : ProjectionDefinition<TInput, TOutput>
@@ -1938,14 +1943,19 @@ public override RenderedProjectionDefinition<TOutput> Render(IBsonSerializer<TIn
19381943
}
19391944
return linqProvider.GetAdapter().TranslateExpressionToGroupProjection(_idExpression, _groupExpression, documentSerializer, serializerRegistry, _translationOptions);
19401945
}
1946+
1947+
internal override RenderedProjectionDefinition<TOutput> RenderForFind(IBsonSerializer<TInput> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
1948+
{
1949+
throw new InvalidOperationException();
1950+
}
19411951
}
19421952

1943-
internal sealed class ProjectExpressionProjection<TInput, TOutput> : ProjectionDefinition<TInput, TOutput>
1953+
internal sealed class ExpressionProjectionDefinition<TInput, TOutput> : ProjectionDefinition<TInput, TOutput>
19441954
{
19451955
private readonly Expression<Func<TInput, TOutput>> _expression;
19461956
private readonly ExpressionTranslationOptions _translationOptions;
19471957

1948-
public ProjectExpressionProjection(Expression<Func<TInput, TOutput>> expression, ExpressionTranslationOptions translationOptions)
1958+
public ExpressionProjectionDefinition(Expression<Func<TInput, TOutput>> expression, ExpressionTranslationOptions translationOptions)
19491959
{
19501960
_expression = Ensure.IsNotNull(expression, nameof(expression));
19511961
_translationOptions = translationOptions; // can be null
@@ -1960,6 +1970,11 @@ public override RenderedProjectionDefinition<TOutput> Render(IBsonSerializer<TIn
19601970
{
19611971
return linqProvider.GetAdapter().TranslateExpressionToProjection(_expression, inputSerializer, serializerRegistry, _translationOptions);
19621972
}
1973+
1974+
internal override RenderedProjectionDefinition<TOutput> RenderForFind(IBsonSerializer<TInput> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
1975+
{
1976+
return linqProvider.GetAdapter().TranslateExpressionToFindProjection(_expression, sourceSerializer, serializerRegistry);
1977+
}
19631978
}
19641979

19651980
internal class SortPipelineStageDefinition<TInput> : PipelineStageDefinition<TInput, TInput>

src/MongoDB.Driver/ProjectionDefinition.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ public virtual RenderedProjectionDefinition<TProjection> Render(IBsonSerializer<
147147
/// <returns>A <see cref="RenderedProjectionDefinition{TProjection}"/>.</returns>
148148
public abstract RenderedProjectionDefinition<TProjection> Render(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider);
149149

150+
internal virtual RenderedProjectionDefinition<TProjection> RenderForFind(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
151+
=> Render(sourceSerializer, serializerRegistry, linqProvider);
152+
150153
/// <summary>
151154
/// Performs an implicit conversion from <see cref="BsonDocument"/> to <see cref="ProjectionDefinition{TSource, TProjection}"/>.
152155
/// </summary>

src/MongoDB.Driver/ProjectionDefinitionBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ public ProjectionDefinition<TSource> Exclude(Expression<Func<TSource, object>> f
391391
/// </returns>
392392
public ProjectionDefinition<TSource, TProjection> Expression<TProjection>(Expression<Func<TSource, TProjection>> expression)
393393
{
394-
return new FindExpressionProjectionDefinition<TSource, TProjection>(expression);
394+
return new ExpressionProjectionDefinition<TSource, TProjection>(expression, null);
395395
}
396396

397397
/// <summary>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Linq.Expressions;
18+
using FluentAssertions;
19+
using MongoDB.Bson.Serialization;
20+
using Xunit;
21+
22+
namespace MongoDB.Driver.Tests
23+
{
24+
public class FindExpressionProjectionDefinitionTests
25+
{
26+
[Fact]
27+
public void Projection_to_class_should_work()
28+
=> AssertProjection(
29+
x => new Projection { A = x.A, X = x.B },
30+
"{ A : 1, X : '$B', _id : 0 }");
31+
32+
[Fact]
33+
public void Projection_to_anonymous_type_should_work()
34+
=> AssertProjection(
35+
x => new { x.A, X = x.B },
36+
"{ A : 1, X : '$B', _id : 0 }");
37+
38+
private void AssertProjection<TProjection>(
39+
Expression<Func<Document, TProjection>> expression,
40+
string expectedProjection)
41+
{
42+
var projection = new FindExpressionProjectionDefinition<Document, TProjection>(expression);
43+
44+
var renderedProjection = projection.Render(
45+
BsonSerializer.LookupSerializer<Document>(),
46+
BsonSerializer.SerializerRegistry);
47+
48+
renderedProjection.Document.Should().BeEquivalentTo(expectedProjection);
49+
}
50+
51+
private class Document
52+
{
53+
public string A { get; set; }
54+
55+
public int B { get; set; }
56+
}
57+
58+
private class Projection
59+
{
60+
public string A { get; set; }
61+
62+
public int X { get; set; }
63+
}
64+
}
65+
}

tests/MongoDB.Driver.Tests/IFindFluentExtensionsTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@ public void SortByDescending_ThenByDescending_should_generate_the_correct_sort()
453453

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

459459
private static void AssertSort(IFindFluent<Person, Person> subject, BsonDocument expectedSort)

tests/MongoDB.Driver.Tests/Linq/Linq2ImplementationTestsOnLinq3/MongoQueryableTests.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,9 @@ public void Distinct_document_preceded_by_select_where()
215215

216216
Assert(query,
217217
1,
218-
"{ $project: { 'A': 1, 'B': 1, '_id': 0 } }",
219-
"{ $match: { 'A': 'Awesome' } }",
220-
"{ $group: { '_id': '$$ROOT' } }",
218+
"{ $project: { 'A' : '$A', 'B' : '$B', '_id': 0 } }",
219+
"{ $match: { 'A' : 'Awesome' } }",
220+
"{ $group: { '_id' : '$$ROOT' } }",
221221
"{ $replaceRoot : { newRoot : '$_id' } }");
222222
}
223223

@@ -233,7 +233,7 @@ public void Distinct_document_preceded_by_where_select()
233233
Assert(query,
234234
1,
235235
"{ $match : { 'A' : 'Awesome' } }",
236-
"{ $project : { A : 1, B : 1, _id : 0 } }",
236+
"{ $project : { A : '$A', B : '$B', _id : 0 } }",
237237
"{ $group : { '_id' : '$$ROOT' } }",
238238
"{ $replaceRoot : { newRoot : '$_id' } }");
239239
}
@@ -999,7 +999,7 @@ public void Select_new_of_same()
999999

10001000
Assert(query,
10011001
2,
1002-
"{ $project : { _id : 1, A : 1 } }");
1002+
"{ $project : { _id : '$_id', A : '$A' } }");
10031003
}
10041004

10051005
[Fact]

tests/MongoDB.Driver.Tests/Linq/Linq2ImplementationTestsOnLinq3/Translators/AggregateGroupTranslatorTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public void Should_translate_just_id()
7070
AssertStages(
7171
result.Stages,
7272
"{ $group : { _id : '$A' } }",
73-
"{ $project : { _id : 1 } }");
73+
"{ $project : { _id : '$_id' } }");
7474

7575
result.Value._id.Should().Be("Amazing");
7676
}

tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/Jira/CSharp1555Tests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public void Select_new_Person_should_work()
4343
.Select(p => new Person { Id = p.Id, Name = p.Name });
4444

4545
var stages = Translate(collection, queryable);
46-
AssertStages(stages, "{ $project : { _id : 1, Name : 1 } }");
46+
AssertStages(stages, "{ $project : { _id : '$_id', Name : '$Name' } }");
4747

4848
var result = queryable.ToList().Single();
4949
result.ShouldBeEquivalentTo(new Person { Id = 1, Name = "A" });
@@ -57,7 +57,7 @@ public void Select_new_Person_without_Name_should_work()
5757
.Select(p => new Person { Id = p.Id });
5858

5959
var stages = Translate(collection, queryable);
60-
AssertStages(stages, "{ $project : { _id : 1 } }");
60+
AssertStages(stages, "{ $project : { _id : '$_id' } }");
6161

6262
var result = queryable.ToList().Single();
6363
result.ShouldBeEquivalentTo(new Person { Id = 1, Name = null });
@@ -71,7 +71,7 @@ public void Select_new_Person_without_Id_should_work()
7171
.Select(p => new Person { Name = p.Name });
7272

7373
var stages = Translate(collection, queryable);
74-
AssertStages(stages, "{ $project : { Name : 1, _id : 0 } }");
74+
AssertStages(stages, "{ $project : { Name : '$Name', _id : 0 } }");
7575

7676
var result = queryable.ToList().Single();
7777
result.ShouldBeEquivalentTo(new Person { Id = 0, Name = "A" });

tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/Jira/CSharp2723Tests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ public void Nested_Select_should_work()
115115
}",
116116
@"{
117117
'$project':{
118-
'_id':1,
118+
'_id':'$_id',
119119
'ParentName':'$Name',
120120
'Children':{
121121
'$map':{

tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/Jira/CSharp3614Tests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public void Test()
4242
});
4343

4444
var stages = Translate(collection, queryable);
45-
AssertStages(stages, "{ $project : { _id : 1, PageCount : 1, Author : { $cond : { if : { $eq : ['$Author', null] }, then : null, else : { _id : '$Author._id', Name : '$Author.Name' } } } } }");
45+
AssertStages(stages, "{ $project : { _id : '$_id', PageCount : '$PageCount', Author : { $cond : { if : { $eq : ['$Author', null] }, then : null, else : { _id : '$Author._id', Name : '$Author.Name' } } } } }");
4646

4747
var results = queryable.ToList().OrderBy(r => r.Id).ToList();
4848
results.Should().HaveCount(2);

tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/Jira/CSharp3922Tests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public void Select_with_constructor_call_should_work()
4949
var stages = Translate(collection, queryable);
5050
AssertStages(
5151
stages,
52-
"{ $project : { X : 1, _id : 0 } }",
52+
"{ $project : { X : '$X', _id : 0 } }",
5353
"{ $project : { R : '$X', S : '$Y', _id : 0 } }",
5454
"{ $project : { T : '$R', U : '$S', _id : 0 } }");
5555
}
@@ -67,7 +67,7 @@ public void Select_with_constructor_call_and_property_set_should_work()
6767
var stages = Translate(collection, queryable);
6868
AssertStages(
6969
stages,
70-
"{ $project : { X : 1, Y : { $literal : 123 }, _id : 0 } }",
70+
"{ $project : { X : '$X', Y : { $literal : 123 }, _id : 0 } }",
7171
"{ $project : { R : '$X', S : '$Y', _id : 0 } }",
7272
"{ $project : { T : '$R', U : '$S', _id : 0 } }");
7373
}

0 commit comments

Comments
 (0)