Skip to content

Commit 7008c17

Browse files
CSHARP-2614: Fix logic for projection where one field name starts with another field name.
1 parent 7465329 commit 7008c17

File tree

2 files changed

+90
-4
lines changed

2 files changed

+90
-4
lines changed

src/MongoDB.Driver/Linq/Translators/FindProjectionTranslator.cs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ private FindProjectionTranslator(DocumentExpression documentExpression, Paramete
8989

9090
protected internal override Expression VisitField(FieldExpression node)
9191
{
92-
if (!_fields.Any(x => x.FieldName == node.FieldName
92+
var dottedFieldExpression = GetDottedFieldExpression(node);
93+
94+
if (!_fields.Any(x => x.FieldName == dottedFieldExpression.FieldName
9395
&& x.Serializer.ValueType == node.Serializer.ValueType))
9496
{
9597
// we need to unwind this call...
@@ -100,7 +102,7 @@ protected internal override Expression VisitField(FieldExpression node)
100102
_parameterExpression,
101103
"GetValue",
102104
new[] { node.Type },
103-
Expression.Constant(node.FieldName),
105+
Expression.Constant(dottedFieldExpression.FieldName),
104106
Expression.Constant(node.Serializer.ValueType.GetDefaultValue(), typeof(object)));
105107
}
106108

@@ -156,6 +158,30 @@ protected internal override Expression VisitDocument(DocumentExpression node)
156158
return base.VisitDocument(node);
157159
}
158160

161+
private FieldExpression GetDottedFieldExpression(FieldExpression node)
162+
{
163+
var fieldNames = new List<string> { node.FieldName };
164+
var document = node.Document as FieldExpression;
165+
while (document != null)
166+
{
167+
fieldNames.Add(document.FieldName);
168+
document = document.Document as FieldExpression;
169+
}
170+
171+
string dottedFieldName;
172+
if (fieldNames.Count > 1)
173+
{
174+
fieldNames.Reverse();
175+
dottedFieldName = string.Join(".", fieldNames);
176+
}
177+
else
178+
{
179+
dottedFieldName = fieldNames[0];
180+
}
181+
182+
return new FieldExpression(null, dottedFieldName, node.Serializer, node.Original);
183+
}
184+
159185
private static BsonDocument GetProjectionDocument(IEnumerable<BsonSerializationInfo> used)
160186
{
161187
var includeId = false;
@@ -192,7 +218,12 @@ private static IReadOnlyList<IFieldExpression> GetUniqueFieldsByHierarchy(IEnume
192218
var referenceGroup = referenceGroups.Dequeue();
193219
if (!skippedFields.Contains(referenceGroup.Key))
194220
{
195-
var hierarchicalReferenceGroups = referenceGroups.Where(x => x.Key.StartsWith(referenceGroup.Key));
221+
var hierarchicalReferenceGroups = referenceGroups
222+
.Where(x =>
223+
{
224+
return x.Key.Count(c => c == '.') != referenceGroup.Key.Count(c => c == '.') && // otherwise, fields are on the same nesting level
225+
x.Key.StartsWith(referenceGroup.Key);
226+
});
196227
uniqueFields.AddRange(referenceGroup);
197228
skippedFields.AddRange(hierarchicalReferenceGroups.Select(x => x.Key));
198229
}

tests/MongoDB.Driver.Tests/Linq/Translators/FindProjectionTranslatorTests.cs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,20 @@ public void Should_translate_a_single_top_level_field_with_an_operation()
9292
result.Value.Should().Be("jack");
9393
}
9494

95+
[Fact]
96+
public void Should_translate_a_new_expression_where_one_field_starts_with_another_field()
97+
{
98+
var result = Project(p => new { p.C.E, p.C.E1 }, "{ C : { E : { F : 2, H : 3 }, E1 : { F : 4, H : 5 } } }");
99+
100+
result.Projection.Should().Be("{ \"C.E\" : 1, \"C.E1\" : 1, _id : 0 }");
101+
102+
result.Value.E.F.Should().Be(2);
103+
result.Value.E.H.Should().Be(3);
104+
105+
result.Value.E1.F.Should().Be(4);
106+
result.Value.E1.H.Should().Be(5);
107+
}
108+
95109
[Fact]
96110
public void Should_translate_a_new_expression_with_a_single_top_level_field()
97111
{
@@ -112,6 +126,17 @@ public void Should_translate_a_new_expression_with_a_single_top_level_computed_f
112126
result.Value.FullName.Should().Be("Jack Awesome");
113127
}
114128

129+
[Fact]
130+
public void Should_translate_a_new_expression_with_top_level_fields_when_one_field_starts_with_another()
131+
{
132+
var result = Project(p => new { p.A, p.A1 }, "{ A : \"Jack\", A1 : \"Peter\" }");
133+
134+
result.Projection.Should().Be("{ A : 1, A1 : 1 _id: 0 }");
135+
136+
result.Value.A.Should().Be("Jack");
137+
result.Value.A1.Should().Be("Peter");
138+
}
139+
115140
[Fact]
116141
public void Should_translate_when_a_top_level_field_is_repeated()
117142
{
@@ -144,7 +169,19 @@ public void Should_translate_with_a_single_computed_nested_field()
144169
}
145170

146171
[Fact]
147-
public void Should_translate_with_a_hierarchical_redundancy()
172+
public void Should_translate_with_a_hierarchical_redundancy_and_a_non_top_level_projection()
173+
{
174+
var result = Project(p => new { p.C.E, F = p.C.E.F }, "{ C : { E : { F : 2, H : 3 } } }");
175+
176+
result.Projection.Should().Be("{ \"C.E\" : 1, _id : 0 }");
177+
178+
result.Value.E.H.Should().Be(3);
179+
result.Value.F.Should().Be(2);
180+
result.Value.E.F.Should().Be(2);
181+
}
182+
183+
[Fact]
184+
public void Should_translate_with_a_hierarchical_redundancy_and_a_top_level_projection()
148185
{
149186
var result = Project(p => new { p.C, F = p.C.E.F }, "{ C: { D: \"CEO\", E: { F: 2 } } }");
150187

@@ -155,6 +192,18 @@ public void Should_translate_with_a_hierarchical_redundancy()
155192
result.Value.F.Should().Be(2);
156193
}
157194

195+
[Fact]
196+
public void Should_translate_with_a_hierarchical_redundancy_and_when_one_field_starts_with_another()
197+
{
198+
var result = Project(p => new { p.C.E, F = p.C.E.E1.F }, "{ C : { E : { E1 : { F : 2, H : 3 } } } }");
199+
200+
result.Projection.Should().Be("{ \"C.E\" : 1, _id : 0 }");
201+
202+
result.Value.E.E1.H.Should().Be(3);
203+
result.Value.E.E1.F.Should().Be(2);
204+
result.Value.F.Should().Be(2);
205+
}
206+
158207
[Fact]
159208
public void Should_translate_a_single_top_level_array()
160209
{
@@ -259,6 +308,8 @@ private class Root
259308

260309
public string A { get; set; }
261310

311+
public string A1 { get; set; }
312+
262313
public string B { get; set; }
263314

264315
public C C { get; set; }
@@ -271,6 +322,8 @@ public class C
271322
public string D { get; set; }
272323

273324
public E E { get; set; }
325+
326+
public E E1 { get; set; }
274327
}
275328

276329
public class E
@@ -280,6 +333,8 @@ public class E
280333
public int H { get; set; }
281334

282335
public IEnumerable<string> I { get; set; }
336+
337+
public E E1 { get; set; }
283338
}
284339
}
285340
}

0 commit comments

Comments
 (0)