Permalink
Browse files

Added support for LINQ queries of the form d.p[i] == c (where p is a …

…string property).
  • Loading branch information...
rstam
rstam committed Apr 12, 2012
1 parent fdda52e commit 11427b5220f2ed4be543d0f8552ca312ea112f0a
Showing with 187 additions and 12 deletions.
  1. +12 −12 Driver/Linq/LinqExtensionMethods.cs
  2. +83 −0 Driver/Linq/Translators/SelectQuery.cs
  3. +92 −0 DriverUnitTests/Linq/SelectQueryTests.cs
@@ -35,18 +35,18 @@ public static IQueryable<T> AsQueryable<T>(this MongoCollection collection)
{
var provider = new MongoQueryProvider(collection);
return new MongoQueryable<T>(provider);
- }
-
- /// <summary>
- /// Returns an instance of IQueryable{{T}} for a MongoCollection.
- /// </summary>
- /// <typeparam name="T">The type of the returned documents.</typeparam>
- /// <param name="collection">The name of the collection.</param>
- /// <returns>An instance of IQueryable{{T}} for a MongoCollection.</returns>
- public static IQueryable<T> AsQueryable<T>(this MongoCollection<T> collection)
- {
- var provider = new MongoQueryProvider(collection);
- return new MongoQueryable<T>(provider);
+ }
+
+ /// <summary>
+ /// Returns an instance of IQueryable{{T}} for a MongoCollection.
+ /// </summary>
+ /// <typeparam name="T">The type of the returned documents.</typeparam>
+ /// <param name="collection">The name of the collection.</param>
+ /// <returns>An instance of IQueryable{{T}} for a MongoCollection.</returns>
+ public static IQueryable<T> AsQueryable<T>(this MongoCollection<T> collection)
+ {
+ var provider = new MongoQueryProvider(collection);
+ return new MongoQueryable<T>(provider);
}
}
}
@@ -359,6 +359,12 @@ private IMongoQuery BuildComparisonQuery(BinaryExpression binaryExpression)
return query;
}
+ query = BuildStringIndexQuery(variableExpression, operatorType, constantExpression);
+ if (query != null)
+ {
+ return query;
+ }
+
query = BuildStringLengthQuery(variableExpression, operatorType, constantExpression);
if (query != null)
{
@@ -977,6 +983,83 @@ private IMongoQuery BuildStringIndexOfQuery(Expression variableExpression, Expre
return null;
}
+ private IMongoQuery BuildStringIndexQuery(Expression variableExpression, ExpressionType operatorType, ConstantExpression constantExpression)
+ {
+ var unaryExpression = variableExpression as UnaryExpression;
+ if (unaryExpression == null)
+ {
+ return null;
+ }
+
+ if (unaryExpression.NodeType != ExpressionType.Convert || unaryExpression.Type != typeof(int))
+ {
+ return null;
+ }
+
+ var methodCallExpression = unaryExpression.Operand as MethodCallExpression;
+ if (methodCallExpression == null)
+ {
+ return null;
+ }
+
+ var method = methodCallExpression.Method;
+ if (method.DeclaringType != typeof(string) || method.Name != "get_Chars")
+ {
+ return null;
+ }
+
+ var stringExpression = methodCallExpression.Object;
+ if (stringExpression == null)
+ {
+ return null;
+ }
+
+ var serializationInfo = GetSerializationInfo(stringExpression);
+ if (serializationInfo == null)
+ {
+ return null;
+ }
+
+ var args = methodCallExpression.Arguments.ToArray();
+ if (args.Length != 1)
+ {
+ return null;
+ }
+
+ var indexExpression = args[0] as ConstantExpression;
+ if (indexExpression == null)
+ {
+ return null;
+ }
+ var index = ToInt32(indexExpression);
+
+ if (constantExpression.Type != typeof(int))
+ {
+ return null;
+ }
+ var value = ToInt32(constantExpression);
+
+ var c = new string((char)value, 1);
+ var positiveClass = (c == "-") ? "\\-" : (c == "]") ? "\\]" : Regex.Escape(c);
+ var negativeClass = "[^" + positiveClass + "]";
+
+ string characterClass;
+ switch (operatorType)
+ {
+ case ExpressionType.Equal:
+ characterClass = positiveClass;
+ break;
+ case ExpressionType.NotEqual:
+ characterClass = negativeClass;
+ break;
+ default:
+ return null; // TODO: suport other comparison operators?
+ }
+ var pattern = string.Format("^.{{{0}}}{1}", index, characterClass);
+
+ return Query.Matches(serializationInfo.ElementName, new BsonRegularExpression(pattern, "s"));
+ }
+
private IMongoQuery BuildStringLengthQuery(Expression variableExpression, ExpressionType operatorType, ConstantExpression constantExpression)
{
if (constantExpression.Type != typeof(int))
@@ -5232,6 +5232,98 @@ public void TestWhereSStartsWithAbcNot()
Assert.AreEqual(4, Consume(query));
}
+ [Test]
+ public void TestWhereSSub1EqualsB()
+ {
+ var query = from c in _collection.AsQueryable<C>()
+ where c.S[1] == 'b'
+ select c;
+
+ var translatedQuery = MongoQueryTranslator.Translate(query);
+ Assert.IsInstanceOf<SelectQuery>(translatedQuery);
+ Assert.AreSame(_collection, translatedQuery.Collection);
+ Assert.AreSame(typeof(C), translatedQuery.DocumentType);
+
+ var selectQuery = (SelectQuery)translatedQuery;
+ Assert.AreEqual("(C c) => ((Int32)c.S.get_Chars(1) == 98)", ExpressionFormatter.ToString(selectQuery.Where));
+ Assert.IsNull(selectQuery.OrderBy);
+ Assert.IsNull(selectQuery.Projection);
+ Assert.IsNull(selectQuery.Skip);
+ Assert.IsNull(selectQuery.Take);
+
+ Assert.AreEqual("{ \"s\" : /^.{1}b/s }", selectQuery.BuildQuery().ToJson());
+ Assert.AreEqual(1, Consume(query));
+ }
+
+ [Test]
+ public void TestWhereSSub1EqualsBNot()
+ {
+ var query = from c in _collection.AsQueryable<C>()
+ where !(c.S[1] == 'b')
+ select c;
+
+ var translatedQuery = MongoQueryTranslator.Translate(query);
+ Assert.IsInstanceOf<SelectQuery>(translatedQuery);
+ Assert.AreSame(_collection, translatedQuery.Collection);
+ Assert.AreSame(typeof(C), translatedQuery.DocumentType);
+
+ var selectQuery = (SelectQuery)translatedQuery;
+ Assert.AreEqual("(C c) => !((Int32)c.S.get_Chars(1) == 98)", ExpressionFormatter.ToString(selectQuery.Where));
+ Assert.IsNull(selectQuery.OrderBy);
+ Assert.IsNull(selectQuery.Projection);
+ Assert.IsNull(selectQuery.Skip);
+ Assert.IsNull(selectQuery.Take);
+
+ Assert.AreEqual("{ \"s\" : { \"$not\" : /^.{1}b/s } }", selectQuery.BuildQuery().ToJson());
+ Assert.AreEqual(4, Consume(query));
+ }
+
+ [Test]
+ public void TestWhereSSub1NotEqualsB()
+ {
+ var query = from c in _collection.AsQueryable<C>()
+ where c.S[1] != 'b'
+ select c;
+
+ var translatedQuery = MongoQueryTranslator.Translate(query);
+ Assert.IsInstanceOf<SelectQuery>(translatedQuery);
+ Assert.AreSame(_collection, translatedQuery.Collection);
+ Assert.AreSame(typeof(C), translatedQuery.DocumentType);
+
+ var selectQuery = (SelectQuery)translatedQuery;
+ Assert.AreEqual("(C c) => ((Int32)c.S.get_Chars(1) != 98)", ExpressionFormatter.ToString(selectQuery.Where));
+ Assert.IsNull(selectQuery.OrderBy);
+ Assert.IsNull(selectQuery.Projection);
+ Assert.IsNull(selectQuery.Skip);
+ Assert.IsNull(selectQuery.Take);
+
+ Assert.AreEqual("{ \"s\" : /^.{1}[^b]/s }", selectQuery.BuildQuery().ToJson());
+ Assert.AreEqual(1, Consume(query));
+ }
+
+ [Test]
+ public void TestWhereSSub1NotEqualsBNot()
+ {
+ var query = from c in _collection.AsQueryable<C>()
+ where !(c.S[1] != 'b')
+ select c;
+
+ var translatedQuery = MongoQueryTranslator.Translate(query);
+ Assert.IsInstanceOf<SelectQuery>(translatedQuery);
+ Assert.AreSame(_collection, translatedQuery.Collection);
+ Assert.AreSame(typeof(C), translatedQuery.DocumentType);
+
+ var selectQuery = (SelectQuery)translatedQuery;
+ Assert.AreEqual("(C c) => !((Int32)c.S.get_Chars(1) != 98)", ExpressionFormatter.ToString(selectQuery.Where));
+ Assert.IsNull(selectQuery.OrderBy);
+ Assert.IsNull(selectQuery.Projection);
+ Assert.IsNull(selectQuery.Skip);
+ Assert.IsNull(selectQuery.Take);
+
+ Assert.AreEqual("{ \"s\" : { \"$not\" : /^.{1}[^b]/s } }", selectQuery.BuildQuery().ToJson());
+ Assert.AreEqual(4, Consume(query));
+ }
+
[Test]
public void TestWhereSTrimContainsXyz()
{

0 comments on commit 11427b5

Please sign in to comment.