Skip to content

Commit 8bf97a8

Browse files
author
rstam
committed
Additional work on CSHARP-433. Implemented "is" operator in LINQ queries.
1 parent 3b46a19 commit 8bf97a8

File tree

5 files changed

+195
-11
lines changed

5 files changed

+195
-11
lines changed

Driver/Linq/Expressions/ExpressionFormatter.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,11 @@ protected override Expression VisitParameter(ParameterExpression node)
362362
/// <returns>The TypeBinaryExpression.</returns>
363363
protected override Expression VisitTypeBinary(TypeBinaryExpression node)
364364
{
365-
_sb.Append("<TypeBinaryExpression>");
365+
_sb.Append("(");
366+
Visit(node.Expression);
367+
_sb.Append(" is ");
368+
_sb.Append(FriendlyClassName(node.TypeOperand));
369+
_sb.Append(")");
366370
return node;
367371
}
368372

Driver/Linq/Expressions/ExpressionPrettyPrinter.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,14 @@ protected override Expression VisitParameter(ParameterExpression node)
332332
/// <returns>The TypeBinaryExpression.</returns>
333333
protected override Expression VisitTypeBinary(TypeBinaryExpression node)
334334
{
335-
throw new NotImplementedException();
335+
WriteHeader(node);
336+
using (new Indentation(this))
337+
{
338+
WriteLine("TypeOperand={0}", FriendlyClassName(node.TypeOperand));
339+
WriteLine("Expression:");
340+
VisitIndented(node.Expression);
341+
}
342+
return node;
336343
}
337344

338345
/// <summary>

Driver/Linq/Translators/SelectQuery.cs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,9 @@ private IMongoQuery BuildQuery(Expression expression)
844844
case ExpressionType.OrElse:
845845
query = BuildOrElseQuery((BinaryExpression)expression);
846846
break;
847+
case ExpressionType.TypeIs:
848+
query = BuildTypeIsQuery((TypeBinaryExpression)expression);
849+
break;
847850
}
848851

849852
if (query == null)
@@ -1248,6 +1251,26 @@ private IMongoQuery BuildStringQuery(MethodCallExpression methodCallExpression)
12481251
return null;
12491252
}
12501253

1254+
private IMongoQuery BuildTypeIsQuery(TypeBinaryExpression typeBinaryExpression)
1255+
{
1256+
var nominalType = typeBinaryExpression.Expression.Type;
1257+
var actualType = typeBinaryExpression.TypeOperand;
1258+
1259+
var discriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(nominalType);
1260+
var discriminator = discriminatorConvention.GetDiscriminator(nominalType, actualType);
1261+
if (discriminator == null)
1262+
{
1263+
return Query.Not("_").Mod(1, 2); // best query I could come up with that's always true
1264+
}
1265+
1266+
if (discriminator.IsBsonArray)
1267+
{
1268+
discriminator = discriminator.AsBsonArray[discriminator.AsBsonArray.Count - 1];
1269+
}
1270+
1271+
return Query.EQ(discriminatorConvention.ElementName, discriminator);
1272+
}
1273+
12511274
private void CombinePredicateWithWhereClause(MethodCallExpression methodCallExpression, LambdaExpression predicate)
12521275
{
12531276
if (predicate != null)
@@ -1870,25 +1893,25 @@ private void TranslateOfType(MethodCallExpression methodCallExpression)
18701893
}
18711894
var nominalType = sourceExpression.Type.GetGenericArguments()[0];
18721895

1873-
if (nominalType == actualType)
1874-
{
1875-
return; // nothing to do
1876-
}
1877-
18781896
if (_projection != null)
18791897
{
18801898
throw new NotSupportedException("OfType after a projection is not supported.");
18811899
}
18821900

18831901
var discriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(nominalType);
18841902
var discriminator = discriminatorConvention.GetDiscriminator(nominalType, actualType);
1903+
if (discriminator == null)
1904+
{
1905+
return; // nothing to do
1906+
}
1907+
18851908
if (discriminator.IsBsonArray)
18861909
{
18871910
discriminator = discriminator.AsBsonArray[discriminator.AsBsonArray.Count - 1];
18881911
}
1912+
var query = Query.EQ(discriminatorConvention.ElementName, discriminator);
18891913

18901914
var injectMethodInfo = typeof(LinqToMongo).GetMethod("Inject");
1891-
var query = Query.EQ("_t", discriminator);
18921915
var body = Expression.Call(injectMethodInfo, Expression.Constant(query));
18931916
var parameter = Expression.Parameter(nominalType, "x");
18941917
var predicate = Expression.Lambda(body, parameter);

DriverUnitTests/Linq/SelectOfTypeHierarchicalTests.cs

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,14 @@ public void TestOfTypeB()
7575
Assert.AreSame(typeof(B), translatedQuery.DocumentType);
7676

7777
var selectQuery = (SelectQuery)translatedQuery;
78-
Assert.IsNull(selectQuery.Where);
79-
Assert.AreEqual(null, selectQuery.OfType); // OfType ignored because <T> was the same as <TDocument>
78+
Assert.AreEqual("(B x) => LinqToMongo.Inject({ \"_t\" : \"B\" })", ExpressionFormatter.ToString(selectQuery.Where));
79+
Assert.AreEqual(typeof(B), selectQuery.OfType);
8080
Assert.IsNull(selectQuery.OrderBy);
8181
Assert.IsNull(selectQuery.Projection);
8282
Assert.IsNull(selectQuery.Skip);
8383
Assert.IsNull(selectQuery.Take);
8484

85-
Assert.IsNull(selectQuery.BuildQuery());
85+
Assert.AreEqual("{ \"_t\" : \"B\" }", selectQuery.BuildQuery().ToJson());
8686
Assert.AreEqual(3, Consume(query));
8787
}
8888

@@ -174,6 +174,81 @@ public void TestWhereBGreaterThan0OfTypeCWhereCGreaterThan0()
174174
Assert.AreEqual(2, Consume(query));
175175
}
176176

177+
[Test]
178+
public void TestWhereBIsB()
179+
{
180+
var query =
181+
from b in _collection.AsQueryable<B>()
182+
where b is B
183+
select b;
184+
185+
var translatedQuery = MongoQueryTranslator.Translate(query);
186+
Assert.IsInstanceOf<SelectQuery>(translatedQuery);
187+
Assert.AreSame(_collection, translatedQuery.Collection);
188+
Assert.AreSame(typeof(B), translatedQuery.DocumentType);
189+
190+
var selectQuery = (SelectQuery)translatedQuery;
191+
Assert.AreEqual("(B b) => (b is B)", ExpressionFormatter.ToString(selectQuery.Where));
192+
Assert.AreEqual(null, selectQuery.OfType); // OfType ignored because <T> was the same as <TDocument>
193+
Assert.IsNull(selectQuery.OrderBy);
194+
Assert.IsNull(selectQuery.Projection);
195+
Assert.IsNull(selectQuery.Skip);
196+
Assert.IsNull(selectQuery.Take);
197+
198+
Assert.AreEqual("{ \"_t\" : \"B\" }", selectQuery.BuildQuery().ToJson());
199+
Assert.AreEqual(3, Consume(query));
200+
}
201+
202+
[Test]
203+
public void TestWhereBIsC()
204+
{
205+
var query =
206+
from b in _collection.AsQueryable<B>()
207+
where b is C
208+
select b;
209+
210+
var translatedQuery = MongoQueryTranslator.Translate(query);
211+
Assert.IsInstanceOf<SelectQuery>(translatedQuery);
212+
Assert.AreSame(_collection, translatedQuery.Collection);
213+
Assert.AreSame(typeof(B), translatedQuery.DocumentType);
214+
215+
var selectQuery = (SelectQuery)translatedQuery;
216+
Assert.AreEqual("(B b) => (b is C)", ExpressionFormatter.ToString(selectQuery.Where));
217+
Assert.AreEqual(null, selectQuery.OfType);
218+
Assert.IsNull(selectQuery.OrderBy);
219+
Assert.IsNull(selectQuery.Projection);
220+
Assert.IsNull(selectQuery.Skip);
221+
Assert.IsNull(selectQuery.Take);
222+
223+
Assert.AreEqual("{ \"_t\" : \"C\" }", selectQuery.BuildQuery().ToJson());
224+
Assert.AreEqual(2, Consume(query));
225+
}
226+
227+
[Test]
228+
public void TestWhereBIsD()
229+
{
230+
var query =
231+
from b in _collection.AsQueryable<B>()
232+
where b is D
233+
select b;
234+
235+
var translatedQuery = MongoQueryTranslator.Translate(query);
236+
Assert.IsInstanceOf<SelectQuery>(translatedQuery);
237+
Assert.AreSame(_collection, translatedQuery.Collection);
238+
Assert.AreSame(typeof(B), translatedQuery.DocumentType);
239+
240+
var selectQuery = (SelectQuery)translatedQuery;
241+
Assert.AreEqual("(B b) => (b is D)", ExpressionFormatter.ToString(selectQuery.Where));
242+
Assert.AreEqual(null, selectQuery.OfType);
243+
Assert.IsNull(selectQuery.OrderBy);
244+
Assert.IsNull(selectQuery.Projection);
245+
Assert.IsNull(selectQuery.Skip);
246+
Assert.IsNull(selectQuery.Take);
247+
248+
Assert.AreEqual("{ \"_t\" : \"D\" }", selectQuery.BuildQuery().ToJson());
249+
Assert.AreEqual(1, Consume(query));
250+
}
251+
177252
private int Consume<T>(IQueryable<T> query)
178253
{
179254
var count = 0;

DriverUnitTests/Linq/SelectOfTypeTests.cs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,81 @@ public void TestWhereBGreaterThan0OfTypeCWhereCGreaterThan0()
173173
Assert.AreEqual(1, Consume(query)); // should match 2 but for that you need to use the hierarchical discriminator
174174
}
175175

176+
[Test]
177+
public void TestWhereBIsB()
178+
{
179+
var query =
180+
from b in _collection.AsQueryable<B>()
181+
where b is B
182+
select b;
183+
184+
var translatedQuery = MongoQueryTranslator.Translate(query);
185+
Assert.IsInstanceOf<SelectQuery>(translatedQuery);
186+
Assert.AreSame(_collection, translatedQuery.Collection);
187+
Assert.AreSame(typeof(B), translatedQuery.DocumentType);
188+
189+
var selectQuery = (SelectQuery)translatedQuery;
190+
Assert.AreEqual("(B b) => (b is B)", ExpressionFormatter.ToString(selectQuery.Where));
191+
Assert.AreEqual(null, selectQuery.OfType); // OfType ignored because <T> was the same as <TDocument>
192+
Assert.IsNull(selectQuery.OrderBy);
193+
Assert.IsNull(selectQuery.Projection);
194+
Assert.IsNull(selectQuery.Skip);
195+
Assert.IsNull(selectQuery.Take);
196+
197+
Assert.AreEqual("{ \"_\" : { \"$not\" : { \"$mod\" : [1, 2] } } }", selectQuery.BuildQuery().ToJson());
198+
Assert.AreEqual(3, Consume(query));
199+
}
200+
201+
[Test]
202+
public void TestWhereBIsC()
203+
{
204+
var query =
205+
from b in _collection.AsQueryable<B>()
206+
where b is C
207+
select b;
208+
209+
var translatedQuery = MongoQueryTranslator.Translate(query);
210+
Assert.IsInstanceOf<SelectQuery>(translatedQuery);
211+
Assert.AreSame(_collection, translatedQuery.Collection);
212+
Assert.AreSame(typeof(B), translatedQuery.DocumentType);
213+
214+
var selectQuery = (SelectQuery)translatedQuery;
215+
Assert.AreEqual("(B b) => (b is C)", ExpressionFormatter.ToString(selectQuery.Where));
216+
Assert.AreEqual(null, selectQuery.OfType);
217+
Assert.IsNull(selectQuery.OrderBy);
218+
Assert.IsNull(selectQuery.Projection);
219+
Assert.IsNull(selectQuery.Skip);
220+
Assert.IsNull(selectQuery.Take);
221+
222+
Assert.AreEqual("{ \"_t\" : \"C\" }", selectQuery.BuildQuery().ToJson());
223+
Assert.AreEqual(1, Consume(query)); // should match 2 but for that you need to use the hierarchical discriminator
224+
}
225+
226+
[Test]
227+
public void TestWhereBIsD()
228+
{
229+
var query =
230+
from b in _collection.AsQueryable<B>()
231+
where b is D
232+
select b;
233+
234+
var translatedQuery = MongoQueryTranslator.Translate(query);
235+
Assert.IsInstanceOf<SelectQuery>(translatedQuery);
236+
Assert.AreSame(_collection, translatedQuery.Collection);
237+
Assert.AreSame(typeof(B), translatedQuery.DocumentType);
238+
239+
var selectQuery = (SelectQuery)translatedQuery;
240+
Assert.AreEqual("(B b) => (b is D)", ExpressionFormatter.ToString(selectQuery.Where));
241+
Assert.AreEqual(null, selectQuery.OfType);
242+
Assert.IsNull(selectQuery.OrderBy);
243+
Assert.IsNull(selectQuery.Projection);
244+
Assert.IsNull(selectQuery.Skip);
245+
Assert.IsNull(selectQuery.Take);
246+
247+
Assert.AreEqual("{ \"_t\" : \"D\" }", selectQuery.BuildQuery().ToJson());
248+
Assert.AreEqual(1, Consume(query));
249+
}
250+
176251
private int Consume<T>(IQueryable<T> query)
177252
{
178253
var count = 0;

0 commit comments

Comments
 (0)