Skip to content

Commit 11427b5

Browse files
author
rstam
committed
Added support for LINQ queries of the form d.p[i] == c (where p is a string property).
1 parent fdda52e commit 11427b5

File tree

3 files changed

+187
-12
lines changed

3 files changed

+187
-12
lines changed

Driver/Linq/LinqExtensionMethods.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,18 @@ public static IQueryable<T> AsQueryable<T>(this MongoCollection collection)
3535
{
3636
var provider = new MongoQueryProvider(collection);
3737
return new MongoQueryable<T>(provider);
38-
}
39-
40-
/// <summary>
41-
/// Returns an instance of IQueryable{{T}} for a MongoCollection.
42-
/// </summary>
43-
/// <typeparam name="T">The type of the returned documents.</typeparam>
44-
/// <param name="collection">The name of the collection.</param>
45-
/// <returns>An instance of IQueryable{{T}} for a MongoCollection.</returns>
46-
public static IQueryable<T> AsQueryable<T>(this MongoCollection<T> collection)
47-
{
48-
var provider = new MongoQueryProvider(collection);
49-
return new MongoQueryable<T>(provider);
38+
}
39+
40+
/// <summary>
41+
/// Returns an instance of IQueryable{{T}} for a MongoCollection.
42+
/// </summary>
43+
/// <typeparam name="T">The type of the returned documents.</typeparam>
44+
/// <param name="collection">The name of the collection.</param>
45+
/// <returns>An instance of IQueryable{{T}} for a MongoCollection.</returns>
46+
public static IQueryable<T> AsQueryable<T>(this MongoCollection<T> collection)
47+
{
48+
var provider = new MongoQueryProvider(collection);
49+
return new MongoQueryable<T>(provider);
5050
}
5151
}
5252
}

Driver/Linq/Translators/SelectQuery.cs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,12 @@ private IMongoQuery BuildComparisonQuery(BinaryExpression binaryExpression)
359359
return query;
360360
}
361361

362+
query = BuildStringIndexQuery(variableExpression, operatorType, constantExpression);
363+
if (query != null)
364+
{
365+
return query;
366+
}
367+
362368
query = BuildStringLengthQuery(variableExpression, operatorType, constantExpression);
363369
if (query != null)
364370
{
@@ -977,6 +983,83 @@ private IMongoQuery BuildStringIndexOfQuery(Expression variableExpression, Expre
977983
return null;
978984
}
979985

986+
private IMongoQuery BuildStringIndexQuery(Expression variableExpression, ExpressionType operatorType, ConstantExpression constantExpression)
987+
{
988+
var unaryExpression = variableExpression as UnaryExpression;
989+
if (unaryExpression == null)
990+
{
991+
return null;
992+
}
993+
994+
if (unaryExpression.NodeType != ExpressionType.Convert || unaryExpression.Type != typeof(int))
995+
{
996+
return null;
997+
}
998+
999+
var methodCallExpression = unaryExpression.Operand as MethodCallExpression;
1000+
if (methodCallExpression == null)
1001+
{
1002+
return null;
1003+
}
1004+
1005+
var method = methodCallExpression.Method;
1006+
if (method.DeclaringType != typeof(string) || method.Name != "get_Chars")
1007+
{
1008+
return null;
1009+
}
1010+
1011+
var stringExpression = methodCallExpression.Object;
1012+
if (stringExpression == null)
1013+
{
1014+
return null;
1015+
}
1016+
1017+
var serializationInfo = GetSerializationInfo(stringExpression);
1018+
if (serializationInfo == null)
1019+
{
1020+
return null;
1021+
}
1022+
1023+
var args = methodCallExpression.Arguments.ToArray();
1024+
if (args.Length != 1)
1025+
{
1026+
return null;
1027+
}
1028+
1029+
var indexExpression = args[0] as ConstantExpression;
1030+
if (indexExpression == null)
1031+
{
1032+
return null;
1033+
}
1034+
var index = ToInt32(indexExpression);
1035+
1036+
if (constantExpression.Type != typeof(int))
1037+
{
1038+
return null;
1039+
}
1040+
var value = ToInt32(constantExpression);
1041+
1042+
var c = new string((char)value, 1);
1043+
var positiveClass = (c == "-") ? "\\-" : (c == "]") ? "\\]" : Regex.Escape(c);
1044+
var negativeClass = "[^" + positiveClass + "]";
1045+
1046+
string characterClass;
1047+
switch (operatorType)
1048+
{
1049+
case ExpressionType.Equal:
1050+
characterClass = positiveClass;
1051+
break;
1052+
case ExpressionType.NotEqual:
1053+
characterClass = negativeClass;
1054+
break;
1055+
default:
1056+
return null; // TODO: suport other comparison operators?
1057+
}
1058+
var pattern = string.Format("^.{{{0}}}{1}", index, characterClass);
1059+
1060+
return Query.Matches(serializationInfo.ElementName, new BsonRegularExpression(pattern, "s"));
1061+
}
1062+
9801063
private IMongoQuery BuildStringLengthQuery(Expression variableExpression, ExpressionType operatorType, ConstantExpression constantExpression)
9811064
{
9821065
if (constantExpression.Type != typeof(int))

DriverUnitTests/Linq/SelectQueryTests.cs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5232,6 +5232,98 @@ public void TestWhereSStartsWithAbcNot()
52325232
Assert.AreEqual(4, Consume(query));
52335233
}
52345234

5235+
[Test]
5236+
public void TestWhereSSub1EqualsB()
5237+
{
5238+
var query = from c in _collection.AsQueryable<C>()
5239+
where c.S[1] == 'b'
5240+
select c;
5241+
5242+
var translatedQuery = MongoQueryTranslator.Translate(query);
5243+
Assert.IsInstanceOf<SelectQuery>(translatedQuery);
5244+
Assert.AreSame(_collection, translatedQuery.Collection);
5245+
Assert.AreSame(typeof(C), translatedQuery.DocumentType);
5246+
5247+
var selectQuery = (SelectQuery)translatedQuery;
5248+
Assert.AreEqual("(C c) => ((Int32)c.S.get_Chars(1) == 98)", ExpressionFormatter.ToString(selectQuery.Where));
5249+
Assert.IsNull(selectQuery.OrderBy);
5250+
Assert.IsNull(selectQuery.Projection);
5251+
Assert.IsNull(selectQuery.Skip);
5252+
Assert.IsNull(selectQuery.Take);
5253+
5254+
Assert.AreEqual("{ \"s\" : /^.{1}b/s }", selectQuery.BuildQuery().ToJson());
5255+
Assert.AreEqual(1, Consume(query));
5256+
}
5257+
5258+
[Test]
5259+
public void TestWhereSSub1EqualsBNot()
5260+
{
5261+
var query = from c in _collection.AsQueryable<C>()
5262+
where !(c.S[1] == 'b')
5263+
select c;
5264+
5265+
var translatedQuery = MongoQueryTranslator.Translate(query);
5266+
Assert.IsInstanceOf<SelectQuery>(translatedQuery);
5267+
Assert.AreSame(_collection, translatedQuery.Collection);
5268+
Assert.AreSame(typeof(C), translatedQuery.DocumentType);
5269+
5270+
var selectQuery = (SelectQuery)translatedQuery;
5271+
Assert.AreEqual("(C c) => !((Int32)c.S.get_Chars(1) == 98)", ExpressionFormatter.ToString(selectQuery.Where));
5272+
Assert.IsNull(selectQuery.OrderBy);
5273+
Assert.IsNull(selectQuery.Projection);
5274+
Assert.IsNull(selectQuery.Skip);
5275+
Assert.IsNull(selectQuery.Take);
5276+
5277+
Assert.AreEqual("{ \"s\" : { \"$not\" : /^.{1}b/s } }", selectQuery.BuildQuery().ToJson());
5278+
Assert.AreEqual(4, Consume(query));
5279+
}
5280+
5281+
[Test]
5282+
public void TestWhereSSub1NotEqualsB()
5283+
{
5284+
var query = from c in _collection.AsQueryable<C>()
5285+
where c.S[1] != 'b'
5286+
select c;
5287+
5288+
var translatedQuery = MongoQueryTranslator.Translate(query);
5289+
Assert.IsInstanceOf<SelectQuery>(translatedQuery);
5290+
Assert.AreSame(_collection, translatedQuery.Collection);
5291+
Assert.AreSame(typeof(C), translatedQuery.DocumentType);
5292+
5293+
var selectQuery = (SelectQuery)translatedQuery;
5294+
Assert.AreEqual("(C c) => ((Int32)c.S.get_Chars(1) != 98)", ExpressionFormatter.ToString(selectQuery.Where));
5295+
Assert.IsNull(selectQuery.OrderBy);
5296+
Assert.IsNull(selectQuery.Projection);
5297+
Assert.IsNull(selectQuery.Skip);
5298+
Assert.IsNull(selectQuery.Take);
5299+
5300+
Assert.AreEqual("{ \"s\" : /^.{1}[^b]/s }", selectQuery.BuildQuery().ToJson());
5301+
Assert.AreEqual(1, Consume(query));
5302+
}
5303+
5304+
[Test]
5305+
public void TestWhereSSub1NotEqualsBNot()
5306+
{
5307+
var query = from c in _collection.AsQueryable<C>()
5308+
where !(c.S[1] != 'b')
5309+
select c;
5310+
5311+
var translatedQuery = MongoQueryTranslator.Translate(query);
5312+
Assert.IsInstanceOf<SelectQuery>(translatedQuery);
5313+
Assert.AreSame(_collection, translatedQuery.Collection);
5314+
Assert.AreSame(typeof(C), translatedQuery.DocumentType);
5315+
5316+
var selectQuery = (SelectQuery)translatedQuery;
5317+
Assert.AreEqual("(C c) => !((Int32)c.S.get_Chars(1) != 98)", ExpressionFormatter.ToString(selectQuery.Where));
5318+
Assert.IsNull(selectQuery.OrderBy);
5319+
Assert.IsNull(selectQuery.Projection);
5320+
Assert.IsNull(selectQuery.Skip);
5321+
Assert.IsNull(selectQuery.Take);
5322+
5323+
Assert.AreEqual("{ \"s\" : { \"$not\" : /^.{1}[^b]/s } }", selectQuery.BuildQuery().ToJson());
5324+
Assert.AreEqual(4, Consume(query));
5325+
}
5326+
52355327
[Test]
52365328
public void TestWhereSTrimContainsXyz()
52375329
{

0 commit comments

Comments
 (0)