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+
}

0 commit comments

Comments
 (0)