Skip to content

Commit efeda17

Browse files
CSHARP-2460: Add throwing exception if linq expressions use unreachable query members.
1 parent 2ff788b commit efeda17

File tree

6 files changed

+314
-4
lines changed

6 files changed

+314
-4
lines changed

src/MongoDB.Driver/Linq/Expressions/FieldExpression.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@
2020

2121
namespace MongoDB.Driver.Linq.Expressions
2222
{
23-
internal sealed class FieldExpression : SerializationExpression, IFieldExpression
23+
internal sealed class FieldExpression : SerializationExpression, IFieldExpression, IHasOutOfCurrentScopePrefix
2424
{
2525
private readonly Expression _document;
2626
private readonly string _fieldName;
2727
private readonly Expression _original;
2828
private readonly IBsonSerializer _serializer;
29+
private string _outOfCurrentScopePrefix;
2930

3031
public FieldExpression(string fieldName, IBsonSerializer serializer)
3132
: this(null, fieldName, serializer, null)
@@ -70,6 +71,12 @@ public Expression Original
7071
get { return _original; }
7172
}
7273

74+
public string OutOfCurrentScopePrefix
75+
{
76+
get { return _outOfCurrentScopePrefix; }
77+
set { _outOfCurrentScopePrefix = value; }
78+
}
79+
7380
public override IBsonSerializer Serializer
7481
{
7582
get { return _serializer; }
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/* Copyright 2019-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+
namespace MongoDB.Driver.Linq.Expressions
17+
{
18+
internal interface IHasOutOfCurrentScopePrefix
19+
{
20+
string OutOfCurrentScopePrefix { get; set; }
21+
}
22+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/* Copyright 2019-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.Collections.Generic;
17+
using System.Linq.Expressions;
18+
using MongoDB.Driver.Linq.Expressions;
19+
20+
namespace MongoDB.Driver.Linq.Processors
21+
{
22+
/// <summary>
23+
/// The expression visitor to collect all expression fields which have been defined out of the current scope.
24+
/// </summary>
25+
internal sealed class OutOfCurrentScopePrefixCollector : ExtensionExpressionVisitor
26+
{
27+
private readonly HashSet<string> _outOfCurrentScopePrefixCollection = new HashSet<string>();
28+
29+
/// <summary>
30+
/// Collects the list of expression fields which have been defined out of the current scope.
31+
/// </summary>
32+
/// <param name="expression">The expression.</param>
33+
/// <returns>The result collection.</returns>
34+
public static IEnumerable<string> Collect(Expression expression)
35+
{
36+
var outOfScopePrefixCollection = new OutOfCurrentScopePrefixCollector();
37+
outOfScopePrefixCollection.Visit(expression);
38+
return outOfScopePrefixCollection._outOfCurrentScopePrefixCollection;
39+
}
40+
41+
public override Expression Visit(Expression node)
42+
{
43+
var hasOutOfCurrentScopePrefix = node as IHasOutOfCurrentScopePrefix;
44+
if (hasOutOfCurrentScopePrefix != null && !string.IsNullOrWhiteSpace(hasOutOfCurrentScopePrefix.OutOfCurrentScopePrefix))
45+
{
46+
_outOfCurrentScopePrefixCollection.Add(hasOutOfCurrentScopePrefix.OutOfCurrentScopePrefix);
47+
}
48+
49+
return base.Visit(node);
50+
}
51+
}
52+
}

src/MongoDB.Driver/Linq/Processors/SerializationBinder.cs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,12 @@
1515

1616
using System;
1717
using System.Collections.Generic;
18-
using System.Linq;
1918
using System.Linq.Expressions;
2019
using System.Reflection;
2120
using MongoDB.Bson;
2221
using MongoDB.Bson.Serialization;
2322
using MongoDB.Driver.Linq.Expressions;
2423
using MongoDB.Driver.Linq.Processors.EmbeddedPipeline;
25-
using MongoDB.Driver.Support;
2624

2725
namespace MongoDB.Driver.Linq.Processors
2826
{
@@ -37,6 +35,7 @@ public static Expression Bind(Expression node, IBindingContext context, bool isC
3735
private readonly IBindingContext _bindingContext;
3836
private bool _isInEmbeddedPipeline;
3937
private readonly bool _isClientSideProjection;
38+
private bool _isOutOfCurrentScope;
4039

4140
private SerializationBinder(IBindingContext bindingContext, bool isClientSideProjection)
4241
{
@@ -103,12 +102,21 @@ protected override Expression VisitConstant(ConstantExpression node)
103102

104103
protected override Expression VisitLambda<T>(Expression<T> node)
105104
{
105+
var oldIsOutOfCurrentScope = _isOutOfCurrentScope;
106+
if (_isInEmbeddedPipeline)
107+
{
108+
_isOutOfCurrentScope = true;
109+
}
110+
106111
// Don't visit the parameters. We cannot replace a parameter expression
107112
// with a document and we don't have a new parameter type to use because
108113
// we don't know why we are binding yet.
109-
return node.Update(
114+
var result = node.Update(
110115
Visit(node.Body),
111116
node.Parameters);
117+
_isOutOfCurrentScope = oldIsOutOfCurrentScope;
118+
119+
return result;
112120
}
113121

114122
protected override Expression VisitMember(MemberExpression node)
@@ -151,6 +159,12 @@ protected override Expression VisitMember(MemberExpression node)
151159
mex);
152160
}
153161
}
162+
163+
var parameterExpression = (node.Expression as ParameterExpression);
164+
if (_isOutOfCurrentScope && parameterExpression != null)
165+
{
166+
SetOutOfCurrentScopePrefixIfPossible(newNode, parameterExpression.Name);
167+
}
154168
}
155169
}
156170

@@ -397,5 +411,14 @@ private Expression BindClientSideProjector(MethodCallExpression node)
397411

398412
return node;
399413
}
414+
415+
private void SetOutOfCurrentScopePrefixIfPossible(Expression expression, string value)
416+
{
417+
var hasOutOfCurrentScopePrefix = expression as IHasOutOfCurrentScopePrefix;
418+
if (hasOutOfCurrentScopePrefix != null)
419+
{
420+
hasOutOfCurrentScopePrefix.OutOfCurrentScopePrefix = value;
421+
}
422+
}
400423
}
401424
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,8 @@ private FilterDefinition<BsonDocument> TranslatePipelineAny(PipelineExpression n
921921
return null;
922922
}
923923

924+
ValidatePipelineExpressionThrowIfNotValid(whereExpression);
925+
924926
FilterDefinition<BsonDocument> filter;
925927
var renderWithoutElemMatch = CanAnyBeRenderedWithoutElemMatch(whereExpression.Predicate);
926928

@@ -1653,6 +1655,16 @@ private IFieldExpression GetFieldExpression(Expression expression)
16531655
return fieldExpression;
16541656
}
16551657

1658+
private void ValidatePipelineExpressionThrowIfNotValid(WhereExpression whereExpression)
1659+
{
1660+
var outOfCurrentScopePrefixCollection = OutOfCurrentScopePrefixCollector.Collect(whereExpression)?.ToList();
1661+
var unsupportedField = outOfCurrentScopePrefixCollection?.FirstOrDefault();
1662+
if (unsupportedField != null)
1663+
{
1664+
throw new NotSupportedException($"The LINQ expression: {whereExpression} has the member \"{unsupportedField}\" which can not be used to build a correct MongoDB query.");
1665+
}
1666+
}
1667+
16561668
// nested types
16571669
private class DocumentToFieldConverter : ExtensionExpressionVisitor
16581670
{

0 commit comments

Comments
 (0)