You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1423 lines
59 KiB

/* Copyright 2010-2012 10gen Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
using MongoDB.Bson;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Options;
using MongoDB.Driver.Builders;
using MongoDB.Driver.Linq.Utils;
using MongoDB.Bson.Serialization.Conventions;
namespace MongoDB.Driver.Linq
{
/// <summary>
/// Translates an expression tree into an IMongoQuery.
/// </summary>
internal class PredicateTranslator
{
// private fields
private readonly BsonSerializationInfoHelper _serializationInfoHelper;
// constructors
/// <summary>
/// Initializes a new instance of the <see cref="PredicateTranslator"/> class.
/// </summary>
/// <param name="serializationHelper">The serialization helper.</param>
public PredicateTranslator(BsonSerializationInfoHelper serializationHelper)
{
_serializationInfoHelper = serializationHelper;
}
// public methods
/// <summary>
/// Builds an IMongoQuery from an expression.
/// </summary>
/// <param name="expression">The expression.</param>
/// <returns>An IMongoQuery.</returns>
public IMongoQuery BuildQuery(Expression expression)
{
IMongoQuery query = null;
switch (expression.NodeType)
{
case ExpressionType.And:
query = BuildAndQuery((BinaryExpression)expression);
break;
case ExpressionType.AndAlso:
query = BuildAndAlsoQuery((BinaryExpression)expression);
break;
case ExpressionType.ArrayIndex:
query = BuildBooleanQuery(expression);
break;
case ExpressionType.Call:
query = BuildMethodCallQuery((MethodCallExpression)expression);
break;
case ExpressionType.Constant:
query = BuildConstantQuery((ConstantExpression)expression);
break;
case ExpressionType.Equal:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.NotEqual:
query = BuildComparisonQuery((BinaryExpression)expression);
break;
case ExpressionType.MemberAccess:
query = BuildBooleanQuery(expression);
break;
case ExpressionType.Not:
query = BuildNotQuery((UnaryExpression)expression);
break;
case ExpressionType.Or:
query = BuildOrQuery((BinaryExpression)expression);
break;
case ExpressionType.OrElse:
query = BuildOrElseQuery((BinaryExpression)expression);
break;
case ExpressionType.TypeIs:
query = BuildTypeIsQuery((TypeBinaryExpression)expression);
break;
}
if (query == null)
{
var message = string.Format("Unsupported where clause: {0}.", ExpressionFormatter.ToString(expression));
throw new ArgumentException(message);
}
return query;
}
// private methods
private IMongoQuery BuildAndAlsoQuery(BinaryExpression binaryExpression)
{
return Query.And(BuildQuery(binaryExpression.Left), BuildQuery(binaryExpression.Right));
}
private IMongoQuery BuildAndQuery(BinaryExpression binaryExpression)
{
if (binaryExpression.Left.Type == typeof(bool) && binaryExpression.Right.Type == typeof(bool))
{
return BuildAndAlsoQuery(binaryExpression);
}
return null;
}
private IMongoQuery BuildAnyQuery(MethodCallExpression methodCallExpression)
{
if (methodCallExpression.Method.DeclaringType == typeof(Enumerable))
{
var arguments = methodCallExpression.Arguments.ToArray();
var serializationInfo = _serializationInfoHelper.GetSerializationInfo(arguments[0]);
if (arguments.Length == 1)
{
return Query.And(
Query.NE(serializationInfo.ElementName, BsonNull.Value),
Query.Not(Query.Size(serializationInfo.ElementName, 0)));
}
else if (arguments.Length == 2)
{
var itemSerializationInfo = _serializationInfoHelper.GetItemSerializationInfo("Any", serializationInfo);
if (!(itemSerializationInfo.Serializer is IBsonDocumentSerializer))
{
var message = string.Format("Any is only support for items that serialize into documents. The current serializer is {0} and must implement {1} for participation in Any queries.",
BsonUtils.GetFriendlyTypeName(itemSerializationInfo.Serializer.GetType()),
BsonUtils.GetFriendlyTypeName(typeof(IBsonDocumentSerializer)));
throw new NotSupportedException(message);
}
var itemSerializer = itemSerializationInfo.Serializer;
var lambda = (LambdaExpression)arguments[1];
_serializationInfoHelper.RegisterExpressionSerializer(lambda.Parameters[0], itemSerializer);
var query = BuildQuery(lambda.Body);
return Query.ElemMatch(serializationInfo.ElementName, query);
}
}
return null;
}
private IMongoQuery BuildArrayLengthQuery(Expression variableExpression, ExpressionType operatorType, ConstantExpression constantExpression)
{
if (operatorType != ExpressionType.Equal && operatorType != ExpressionType.NotEqual)
{
return null;
}
if (constantExpression.Type != typeof(int))
{
return null;
}
var value = ToInt32(constantExpression);
BsonSerializationInfo serializationInfo = null;
var unaryExpression = variableExpression as UnaryExpression;
if (unaryExpression != null && unaryExpression.NodeType == ExpressionType.ArrayLength)
{
var arrayMemberExpression = unaryExpression.Operand as MemberExpression;
if (arrayMemberExpression != null)
{
serializationInfo = _serializationInfoHelper.GetSerializationInfo(arrayMemberExpression);
}
}
var memberExpression = variableExpression as MemberExpression;
if (memberExpression != null && memberExpression.Member.Name == "Count")
{
var arrayMemberExpression = memberExpression.Expression as MemberExpression;
if (arrayMemberExpression != null)
{
serializationInfo = _serializationInfoHelper.GetSerializationInfo(arrayMemberExpression);
}
}
var methodCallExpression = variableExpression as MethodCallExpression;
if (methodCallExpression != null && methodCallExpression.Method.Name == "Count" && methodCallExpression.Method.DeclaringType == typeof(Enumerable))
{
var arguments = methodCallExpression.Arguments.ToArray();
if (arguments.Length == 1)
{
var arrayMemberExpression = methodCallExpression.Arguments[0] as MemberExpression;
if (arrayMemberExpression != null && arrayMemberExpression.Type != typeof(string))
{
serializationInfo = _serializationInfoHelper.GetSerializationInfo(arrayMemberExpression);
}
}
}
if (serializationInfo != null)
{
if (operatorType == ExpressionType.Equal)
{
return Query.Size(serializationInfo.ElementName, value);
}
else
{
return Query.Not(Query.Size(serializationInfo.ElementName, value));
}
}
return null;
}
private IMongoQuery BuildBooleanQuery(bool value)
{
if (value)
{
return new QueryDocument(); // empty query matches all documents
}
else
{
return Query.Type("_id", (BsonType)(-1)); // matches no documents (and uses _id index when used at top level)
}
}
private IMongoQuery BuildBooleanQuery(Expression expression)
{
if (expression.Type == typeof(bool))
{
var constantExpression = expression as ConstantExpression;
if (constantExpression != null)
{
return BuildBooleanQuery((bool)constantExpression.Value);
}
var serializationInfo = _serializationInfoHelper.GetSerializationInfo(expression);
return new QueryDocument(serializationInfo.ElementName, true);
}
return null;
}
private IMongoQuery BuildComparisonQuery(BinaryExpression binaryExpression)
{
// the constant could be on either side
var variableExpression = binaryExpression.Left;
var constantExpression = binaryExpression.Right as ConstantExpression;
var operatorType = binaryExpression.NodeType;
if (constantExpression == null)
{
return null;
}
var query = BuildArrayLengthQuery(variableExpression, operatorType, constantExpression);
if (query != null)
{
return query;
}
query = BuildModQuery(variableExpression, operatorType, constantExpression);
if (query != null)
{
return query;
}
query = BuildStringIndexOfQuery(variableExpression, operatorType, constantExpression);
if (query != null)
{
return query;
}
query = BuildStringIndexQuery(variableExpression, operatorType, constantExpression);
if (query != null)
{
return query;
}
query = BuildStringLengthQuery(variableExpression, operatorType, constantExpression);
if (query != null)
{
return query;
}
query = BuildStringCaseInsensitiveComparisonQuery(variableExpression, operatorType, constantExpression);
if (query != null)
{
return query;
}
query = BuildTypeComparisonQuery(variableExpression, operatorType, constantExpression);
if (query != null)
{
return query;
}
return BuildComparisonQuery(variableExpression, operatorType, constantExpression);
}
private IMongoQuery BuildComparisonQuery(Expression variableExpression, ExpressionType operatorType, ConstantExpression constantExpression)
{
BsonSerializationInfo serializationInfo = null;
var value = constantExpression.Value;
var unaryExpression = variableExpression as UnaryExpression;
if (unaryExpression != null && (unaryExpression.NodeType == ExpressionType.Convert || unaryExpression.NodeType == ExpressionType.ConvertChecked))
{
if (unaryExpression.Operand.Type.IsEnum)
{
var enumType = unaryExpression.Operand.Type;
if (unaryExpression.Type == Enum.GetUnderlyingType(enumType))
{
serializationInfo = _serializationInfoHelper.GetSerializationInfo(unaryExpression.Operand);
value = Enum.ToObject(enumType, value); // serialize enum instead of underlying integer
}
}
else if (
unaryExpression.Type.IsGenericType &&
unaryExpression.Type.GetGenericTypeDefinition() == typeof(Nullable<>) &&
unaryExpression.Operand.Type.IsGenericType &&
unaryExpression.Operand.Type.GetGenericTypeDefinition() == typeof(Nullable<>) &&
unaryExpression.Operand.Type.GetGenericArguments()[0].IsEnum)
{
var enumType = unaryExpression.Operand.Type.GetGenericArguments()[0];
if (unaryExpression.Type.GetGenericArguments()[0] == Enum.GetUnderlyingType(enumType))
{
serializationInfo = _serializationInfoHelper.GetSerializationInfo(unaryExpression.Operand);
if (value != null)
{
value = Enum.ToObject(enumType, value); // serialize enum instead of underlying integer
}
}
}
}
else
{
serializationInfo = _serializationInfoHelper.GetSerializationInfo(variableExpression);
}
if (serializationInfo != null)
{
var serializedValue = _serializationInfoHelper.SerializeValue(serializationInfo, value);
switch (operatorType)
{
case ExpressionType.Equal: return Query.EQ(serializationInfo.ElementName, serializedValue);
case ExpressionType.GreaterThan: return Query.GT(serializationInfo.ElementName, serializedValue);
case ExpressionType.GreaterThanOrEqual: return Query.GTE(serializationInfo.ElementName, serializedValue);
case ExpressionType.LessThan: return Query.LT(serializationInfo.ElementName, serializedValue);
case ExpressionType.LessThanOrEqual: return Query.LTE(serializationInfo.ElementName, serializedValue);
case ExpressionType.NotEqual: return Query.NE(serializationInfo.ElementName, serializedValue);
}
}
return null;
}
private IMongoQuery BuildConstantQuery(ConstantExpression constantExpression)
{
var value = constantExpression.Value;
if (value != null && value.GetType() == typeof(bool))
{
return BuildBooleanQuery((bool)value);
}
return null;
}
private IMongoQuery BuildContainsAllQuery(MethodCallExpression methodCallExpression)
{
if (methodCallExpression.Method.DeclaringType == typeof(LinqToMongo))
{
var arguments = methodCallExpression.Arguments.ToArray();
if (arguments.Length == 2)
{
var serializationInfo = _serializationInfoHelper.GetSerializationInfo(arguments[0]);
var valuesExpression = arguments[1] as ConstantExpression;
if (valuesExpression != null)
{
var itemSerializationInfo = _serializationInfoHelper.GetItemSerializationInfo("ContainsAll", serializationInfo);
var serializedValues = _serializationInfoHelper.SerializeValues(itemSerializationInfo, (IEnumerable)valuesExpression.Value);
return Query.All(serializationInfo.ElementName, serializedValues);
}
}
}
return null;
}
private IMongoQuery BuildContainsAnyQuery(MethodCallExpression methodCallExpression)
{
if (methodCallExpression.Method.DeclaringType == typeof(LinqToMongo))
{
var arguments = methodCallExpression.Arguments.ToArray();
if (arguments.Length == 2)
{
var serializationInfo = _serializationInfoHelper.GetSerializationInfo(arguments[0]);
var valuesExpression = arguments[1] as ConstantExpression;
if (valuesExpression != null)
{
var itemSerializationInfo = _serializationInfoHelper.GetItemSerializationInfo("ContainsAny", serializationInfo);
var serializedValues = _serializationInfoHelper.SerializeValues(itemSerializationInfo, (IEnumerable)valuesExpression.Value);
return Query.In(serializationInfo.ElementName, serializedValues);
}
}
}
return null;
}
private IMongoQuery BuildContainsKeyQuery(MethodCallExpression methodCallExpression)
{
var dictionaryType = methodCallExpression.Object.Type;
var implementedInterfaces = new List<Type>(dictionaryType.GetInterfaces());
if (dictionaryType.IsInterface)
{
implementedInterfaces.Add(dictionaryType);
}
Type dictionaryGenericInterface = null;
Type dictionaryInterface = null;
foreach (var implementedInterface in implementedInterfaces)
{
if (implementedInterface.IsGenericType)
{
if (implementedInterface.GetGenericTypeDefinition() == typeof(IDictionary<,>))
{
dictionaryGenericInterface = implementedInterface;
}
}
else if (implementedInterface == typeof(IDictionary))
{
dictionaryInterface = implementedInterface;
}
}
Type keyNominalType;
if (dictionaryGenericInterface != null)
{
keyNominalType = dictionaryGenericInterface.GetGenericArguments()[0]; // TKey
}
else if (dictionaryInterface != null)
{
keyNominalType = typeof(object);
}
else
{
return null;
}
var arguments = methodCallExpression.Arguments.ToArray();
if (arguments.Length != 1)
{
return null;
}
var constantExpression = arguments[0] as ConstantExpression;
if (constantExpression == null)
{
return null;
}
var key = constantExpression.Value;
var serializationInfo = _serializationInfoHelper.GetSerializationInfo(methodCallExpression.Object);
var dictionarySerializationOptions = (DictionarySerializationOptions)serializationInfo.SerializationOptions ?? DictionarySerializationOptions.Defaults;
var keyActualType = (key != null) ? key.GetType() : keyNominalType;
var keySerializer = BsonSerializer.LookupSerializer(keyActualType);
var keySerializationInfo = new BsonSerializationInfo(
null, // elementName
keySerializer,
keyNominalType,
dictionarySerializationOptions.KeyValuePairSerializationOptions.KeySerializationOptions);
var serializedKey = _serializationInfoHelper.SerializeValue(keySerializationInfo, key);
switch (dictionarySerializationOptions.Representation)
{
case DictionaryRepresentation.ArrayOfDocuments:
return Query.EQ(serializationInfo.ElementName + ".k", serializedKey);
case DictionaryRepresentation.Document:
return Query.Exists(serializationInfo.ElementName + "." + serializedKey.AsString);
default:
var message = string.Format(
"{0} in a LINQ query is only supported for DictionaryRepresentation ArrayOfDocuments or Document, not {1}.",
methodCallExpression.Method.Name, // could be Contains (for IDictionary) or ContainsKey (for IDictionary<TKey, TValue>)
dictionarySerializationOptions.Representation);
throw new NotSupportedException(message);
}
}
private IMongoQuery BuildContainsQuery(MethodCallExpression methodCallExpression)
{
// handle IDictionary Contains the same way as IDictionary<TKey, TValue> ContainsKey
if (methodCallExpression.Object != null && typeof(IDictionary).IsAssignableFrom(methodCallExpression.Object.Type))
{
return BuildContainsKeyQuery(methodCallExpression);
}
if (methodCallExpression.Method.DeclaringType == typeof(string))
{
return BuildStringQuery(methodCallExpression);
}
if (methodCallExpression.Object != null && methodCallExpression.Object.NodeType == ExpressionType.Constant)
{
return BuildInQuery(methodCallExpression);
}
BsonSerializationInfo serializationInfo = null;
ConstantExpression valueExpression = null;
var arguments = methodCallExpression.Arguments.ToArray();
if (arguments.Length == 1)
{
if (typeof(IEnumerable).IsAssignableFrom(methodCallExpression.Method.DeclaringType))
{
serializationInfo = _serializationInfoHelper.GetSerializationInfo(methodCallExpression.Object);
valueExpression = arguments[0] as ConstantExpression;
}
}
else if (arguments.Length == 2)
{
if (methodCallExpression.Method.DeclaringType == typeof(Enumerable))
{
if (arguments[0].NodeType == ExpressionType.Constant)
{
return BuildInQuery(methodCallExpression);
}
serializationInfo = _serializationInfoHelper.GetSerializationInfo(arguments[0]);
valueExpression = arguments[1] as ConstantExpression;
}
}
if (serializationInfo != null && valueExpression != null)
{
var itemSerializationInfo = _serializationInfoHelper.GetItemSerializationInfo("Contains", serializationInfo);
var serializedValue = _serializationInfoHelper.SerializeValue(itemSerializationInfo, valueExpression.Value);
return Query.EQ(serializationInfo.ElementName, serializedValue);
}
return null;
}
private IMongoQuery BuildEqualsQuery(MethodCallExpression methodCallExpression)
{
var arguments = methodCallExpression.Arguments.ToArray();
// assume that static and instance Equals mean the same thing for all classes (i.e. an equality test)
Expression firstExpression = null;
Expression secondExpression = null;
if (methodCallExpression.Object == null)
{
// static Equals method
if (arguments.Length == 2)
{
firstExpression = arguments[0];
secondExpression = arguments[1];
}
}
else
{
// instance Equals method
if (arguments.Length == 1)
{
firstExpression = methodCallExpression.Object;
secondExpression = arguments[0];
}
}
if (firstExpression != null && secondExpression != null)
{
// the constant could be either expression
var variableExpression = firstExpression;
var constantExpression = secondExpression as ConstantExpression;
if (constantExpression == null)
{
constantExpression = firstExpression as ConstantExpression;
variableExpression = secondExpression;
}
if (constantExpression == null)
{
return null;
}
if (variableExpression.Type == typeof(Type) && constantExpression.Type == typeof(Type))
{
return BuildTypeComparisonQuery(variableExpression, ExpressionType.Equal, constantExpression);
}
return BuildComparisonQuery(variableExpression, ExpressionType.Equal, constantExpression);
}
return null;
}
private IMongoQuery BuildInQuery(MethodCallExpression methodCallExpression)
{
var methodDeclaringType = methodCallExpression.Method.DeclaringType;
var arguments = methodCallExpression.Arguments.ToArray();
BsonSerializationInfo serializationInfo = null;
ConstantExpression valuesExpression = null;
if (methodDeclaringType == typeof(LinqToMongo))
{
if (arguments.Length == 2)
{
serializationInfo = _serializationInfoHelper.GetSerializationInfo(arguments[0]);
valuesExpression = arguments[1] as ConstantExpression;
}
}
else if (methodDeclaringType == typeof(Enumerable) || methodDeclaringType == typeof(Queryable))
{
if (arguments.Length == 2)
{
serializationInfo = _serializationInfoHelper.GetSerializationInfo(arguments[1]);
valuesExpression = arguments[0] as ConstantExpression;
}
}
else
{
if (methodDeclaringType.IsGenericType)
{
methodDeclaringType = methodDeclaringType.GetGenericTypeDefinition();
}
bool contains = methodDeclaringType.GetInterface("ICollection`1") != null;
if (contains && arguments.Length == 1)
{
serializationInfo = _serializationInfoHelper.GetSerializationInfo(arguments[0]);
valuesExpression = methodCallExpression.Object as ConstantExpression;
}
}
if (serializationInfo != null && valuesExpression != null)
{
var serializedValues = _serializationInfoHelper.SerializeValues(serializationInfo, (IEnumerable)valuesExpression.Value);
return Query.In(serializationInfo.ElementName, serializedValues);
}
return null;
}
private IMongoQuery BuildInjectQuery(MethodCallExpression methodCallExpression)
{
if (methodCallExpression.Method.DeclaringType == typeof(LinqToMongo))
{
var arguments = methodCallExpression.Arguments.ToArray();
if (arguments.Length == 1)
{
var queryExpression = arguments[0] as ConstantExpression;
if (queryExpression != null)
{
return (IMongoQuery)queryExpression.Value;
}
}
}
return null;
}
private IMongoQuery BuildIsMatchQuery(MethodCallExpression methodCallExpression)
{
if (methodCallExpression.Method.DeclaringType == typeof(Regex))
{
var arguments = methodCallExpression.Arguments.ToArray();
var obj = methodCallExpression.Object;
if (obj == null)
{
if (arguments.Length == 2 || arguments.Length == 3)
{
var serializationInfo = _serializationInfoHelper.GetSerializationInfo(arguments[0]);
var patternExpression = arguments[1] as ConstantExpression;
if (patternExpression != null)
{
var pattern = patternExpression.Value as string;
if (pattern != null)
{
var options = RegexOptions.None;
if (arguments.Length == 3)
{
var optionsExpression = arguments[2] as ConstantExpression;
if (optionsExpression == null || optionsExpression.Type != typeof(RegexOptions))
{
return null;
}
options = (RegexOptions)optionsExpression.Value;
}
var regex = new Regex(pattern, options);
return Query.Matches(serializationInfo.ElementName, regex);
}
}
}
}
else
{
var regexExpression = obj as ConstantExpression;
if (regexExpression != null && arguments.Length == 1)
{
var serializationInfo = _serializationInfoHelper.GetSerializationInfo(arguments[0]);
var regex = regexExpression.Value as Regex;
if (regex != null)
{
return Query.Matches(serializationInfo.ElementName, regex);
}
}
}
}
return null;
}
private IMongoQuery BuildIsNullOrEmptyQuery(MethodCallExpression methodCallExpression)
{
if (methodCallExpression.Method.DeclaringType == typeof(string) && methodCallExpression.Object == null)
{
var arguments = methodCallExpression.Arguments.ToArray();
if (arguments.Length == 1)
{
var serializationInfo = _serializationInfoHelper.GetSerializationInfo(arguments[0]);
return Query.Or(
Query.Type(serializationInfo.ElementName, BsonType.Null), // this is the safe way to test for null
Query.EQ(serializationInfo.ElementName, ""));
}
}
return null;
}
private IMongoQuery BuildMethodCallQuery(MethodCallExpression methodCallExpression)
{
switch (methodCallExpression.Method.Name)
{
case "Any": return BuildAnyQuery(methodCallExpression);
case "Contains": return BuildContainsQuery(methodCallExpression);
case "ContainsAll": return BuildContainsAllQuery(methodCallExpression);
case "ContainsAny": return BuildContainsAnyQuery(methodCallExpression);
case "ContainsKey": return BuildContainsKeyQuery(methodCallExpression);
case "EndsWith": return BuildStringQuery(methodCallExpression);
case "Equals": return BuildEqualsQuery(methodCallExpression);
case "In": return BuildInQuery(methodCallExpression);
case "Inject": return BuildInjectQuery(methodCallExpression);
case "IsMatch": return BuildIsMatchQuery(methodCallExpression);
case "IsNullOrEmpty": return BuildIsNullOrEmptyQuery(methodCallExpression);
case "StartsWith": return BuildStringQuery(methodCallExpression);
}
return null;
}
private IMongoQuery BuildModQuery(Expression variableExpression, ExpressionType operatorType, ConstantExpression constantExpression)
{
if (operatorType != ExpressionType.Equal && operatorType != ExpressionType.NotEqual)
{
return null;
}
if (constantExpression.Type != typeof(int) && constantExpression.Type != typeof(long))
{
return null;
}
var value = ToInt64(constantExpression);
var modBinaryExpression = variableExpression as BinaryExpression;
if (modBinaryExpression != null && modBinaryExpression.NodeType == ExpressionType.Modulo)
{
var serializationInfo = _serializationInfoHelper.GetSerializationInfo(modBinaryExpression.Left);
var modulusExpression = modBinaryExpression.Right as ConstantExpression;
if (modulusExpression != null)
{
var modulus = ToInt64(modulusExpression);
if (operatorType == ExpressionType.Equal)
{
return Query.Mod(serializationInfo.ElementName, modulus, value);
}
else
{
return Query.Not(Query.Mod(serializationInfo.ElementName, modulus, value));
}
}
}
return null;
}
private IMongoQuery BuildNotQuery(UnaryExpression unaryExpression)
{
var queryDocument = new QueryDocument(BuildQuery(unaryExpression.Operand).ToBsonDocument());
return Query.Not(queryDocument);
}
private IMongoQuery BuildOrElseQuery(BinaryExpression binaryExpression)
{
return Query.Or(BuildQuery(binaryExpression.Left), BuildQuery(binaryExpression.Right));
}
private IMongoQuery BuildOrQuery(BinaryExpression binaryExpression)
{
if (binaryExpression.Left.Type == typeof(bool) && binaryExpression.Right.Type == typeof(bool))
{
return BuildOrElseQuery(binaryExpression);
}
return null;
}
private IMongoQuery BuildStringIndexOfQuery(Expression variableExpression, ExpressionType operatorType, ConstantExpression constantExpression)
{
// TODO: support other comparison operators
if (operatorType != ExpressionType.Equal)
{
return null;
}
if (constantExpression.Type != typeof(int))
{
return null;
}
var index = ToInt32(constantExpression);
var methodCallExpression = variableExpression as MethodCallExpression;
if (methodCallExpression != null &&
(methodCallExpression.Method.Name == "IndexOf" || methodCallExpression.Method.Name == "IndexOfAny") &&
methodCallExpression.Method.DeclaringType == typeof(string))
{
var serializationInfo = _serializationInfoHelper.GetSerializationInfo(methodCallExpression.Object);
object value;
var startIndex = -1;
var count = -1;
var args = methodCallExpression.Arguments.ToArray();
switch (args.Length)
{
case 3:
var countExpression = args[2] as ConstantExpression;
if (countExpression == null)
{
return null;
}
count = ToInt32(countExpression);
goto case 2;
case 2:
var startIndexExpression = args[1] as ConstantExpression;
if (startIndexExpression == null)
{
return null;
}
startIndex = ToInt32(startIndexExpression);
goto case 1;
case 1:
var valueExpression = args[0] as ConstantExpression;
if (valueExpression == null)
{
return null;
}
value = valueExpression.Value;
break;
default:
return null;
}
string pattern = null;
if (value.GetType() == typeof(char) || value.GetType() == typeof(char[]))
{
char[] chars;
if (value.GetType() == typeof(char))
{
chars = new char[] { (char)value };
}
else
{
chars = (char[])value;
}
var positiveClass = string.Join("", chars.Select(c => (c == '-') ? "\\-" : (c == ']') ? "\\]" : Regex.Escape(c.ToString())).ToArray());
var negativeClass = "[^" + positiveClass + "]";
if (chars.Length > 1)
{
positiveClass = "[" + positiveClass + "]";
}
if (startIndex == -1)
{
// the regex for: IndexOf(c) == index
// is: /^[^c]{index}c/
pattern = string.Format("^{0}{{{1}}}{2}", negativeClass, index, positiveClass);
}
else
{
if (count == -1)
{
// the regex for: IndexOf(c, startIndex) == index
// is: /^.{startIndex}[^c]{index - startIndex}c/
pattern = string.Format("^.{{{0}}}{1}{{{2}}}{3}", startIndex, negativeClass, index - startIndex, positiveClass);
}
else
{
if (index >= startIndex + count)
{
// index is outside of the substring so no match is possible
return BuildBooleanQuery(false);
}
else
{
// the regex for: IndexOf(c, startIndex, count) == index
// is: /^.{startIndex}(?=.{count})[^c]{index - startIndex}c/
pattern = string.Format("^.{{{0}}}(?=.{{{1}}}){2}{{{3}}}{4}", startIndex, count, negativeClass, index - startIndex, positiveClass);
}
}
}
}
else if (value.GetType() == typeof(string))
{
var escapedString = Regex.Escape((string)value);
if (startIndex == -1)
{
// the regex for: IndexOf(s) == index
// is: /^(?!.{0,index - 1}s).{index}s/
pattern = string.Format("^(?!.{{0,{2}}}{0}).{{{1}}}{0}", escapedString, index, index - 1);
}
else
{
if (count == -1)
{
// the regex for: IndexOf(s, startIndex) == index
// is: /^.{startIndex}(?!.{0, index - startIndex - 1}s).{index - startIndex}s/
pattern = string.Format("^.{{{1}}}(?!.{{0,{2}}}{0}).{{{3}}}{0}", escapedString, startIndex, index - startIndex - 1, index - startIndex);
}
else
{
var unescapedLength = ((string)value).Length;
if (unescapedLength > startIndex + count - index)
{
// substring isn't long enough to match
return BuildBooleanQuery(false);
}
else
{
// the regex for: IndexOf(s, startIndex, count) == index
// is: /^.{startIndex}(?=.{count})(?!.{0,index - startIndex - 1}s).{index - startIndex)s/
pattern = string.Format("^.{{{1}}}(?=.{{{2}}})(?!.{{0,{3}}}{0}).{{{4}}}{0}", escapedString, startIndex, count, index - startIndex - 1, index - startIndex);
}
}
}
}
if (pattern != null)
{
return Query.Matches(serializationInfo.ElementName, new BsonRegularExpression(pattern, "s"));
}
}
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 args = methodCallExpression.Arguments.ToArray();
if (args.Length != 1)
{
return null;
}
var indexExpression = args[0] as ConstantExpression;
if (indexExpression == null)
{
return null;
}
if (constantExpression.Type != typeof(int))
{
return null;
}
var index = ToInt32(indexExpression);
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);
var serializationInfo = _serializationInfoHelper.GetSerializationInfo(stringExpression);
return Query.Matches(serializationInfo.ElementName, new BsonRegularExpression(pattern, "s"));
}
private IMongoQuery BuildStringLengthQuery(Expression variableExpression, ExpressionType operatorType, ConstantExpression constantExpression)
{
if (constantExpression.Type != typeof(int))
{
return null;
}
var value = ToInt32(constantExpression);
BsonSerializationInfo serializationInfo = null;
var memberExpression = variableExpression as MemberExpression;
if (memberExpression != null && memberExpression.Member.Name == "Length")
{
var stringMemberExpression = memberExpression.Expression as MemberExpression;
if (stringMemberExpression != null && stringMemberExpression.Type == typeof(string))
{
serializationInfo = _serializationInfoHelper.GetSerializationInfo(stringMemberExpression);
}
}
var methodCallExpression = variableExpression as MethodCallExpression;
if (methodCallExpression != null && methodCallExpression.Method.Name == "Count" && methodCallExpression.Method.DeclaringType == typeof(Enumerable))
{
var args = methodCallExpression.Arguments.ToArray();
if (args.Length == 1)
{
var stringMemberExpression = args[0] as MemberExpression;
if (stringMemberExpression != null && stringMemberExpression.Type == typeof(string))
{
serializationInfo = _serializationInfoHelper.GetSerializationInfo(stringMemberExpression);
}
}
}
if (serializationInfo != null)
{
string regex = null;
switch (operatorType)
{
case ExpressionType.NotEqual:
case ExpressionType.Equal: regex = @"/^.{" + value.ToString() + "}$/s"; break;
case ExpressionType.GreaterThan: regex = @"/^.{" + (value + 1).ToString() + ",}$/s"; break;
case ExpressionType.GreaterThanOrEqual: regex = @"/^.{" + value.ToString() + ",}$/s"; break;
case ExpressionType.LessThan: regex = @"/^.{0," + (value - 1).ToString() + "}$/s"; break;
case ExpressionType.LessThanOrEqual: regex = @"/^.{0," + value.ToString() + "}$/s"; break;
}
if (regex != null)
{
if (operatorType == ExpressionType.NotEqual)
{
return Query.Not(Query.Matches(serializationInfo.ElementName, regex));
}
else
{
return Query.Matches(serializationInfo.ElementName, regex);
}
}
}
return null;
}
private IMongoQuery BuildStringCaseInsensitiveComparisonQuery(Expression variableExpression, ExpressionType operatorType, ConstantExpression constantExpression)
{
var methodExpression = variableExpression as MethodCallExpression;
if (methodExpression == null)
{
return null;
}
var methodName = methodExpression.Method.Name;
if ((methodName != "ToLower" && methodName != "ToUpper") ||
methodExpression.Object == null ||
methodExpression.Type != typeof(string) ||
methodExpression.Arguments.Count != 0)
{
return null;
}
if (operatorType != ExpressionType.Equal && operatorType != ExpressionType.NotEqual)
{
return null;
}
var serializationInfo = _serializationInfoHelper.GetSerializationInfo(methodExpression.Object);
var serializedValue = _serializationInfoHelper.SerializeValue(serializationInfo, constantExpression.Value);
if (serializedValue.IsString)
{
var stringValue = serializedValue.AsString;
var stringValueCaseMatches =
methodName == "ToLower" && stringValue == stringValue.ToLower(CultureInfo.InvariantCulture) ||
methodName == "ToUpper" && stringValue == stringValue.ToUpper(CultureInfo.InvariantCulture);
if (stringValueCaseMatches)
{
string pattern = "/^" + Regex.Escape(stringValue) + "$/i";
var regex = new BsonRegularExpression(pattern);
if (operatorType == ExpressionType.Equal)
{
return Query.Matches(serializationInfo.ElementName, regex);
}
else
{
return Query.Not(Query.Matches(serializationInfo.ElementName, regex));
}
}
else
{
if (operatorType == ExpressionType.Equal)
{
// == "mismatched case" matches no documents
return BuildBooleanQuery(false);
}
else
{
// != "mismatched case" matches all documents
return BuildBooleanQuery(true);
}
}
}
else if (serializedValue.IsBsonNull)
{
if (operatorType == ExpressionType.Equal)
{
return Query.EQ(serializationInfo.ElementName, BsonNull.Value);
}
else
{
return Query.NE(serializationInfo.ElementName, BsonNull.Value);
}
}
else
{
var message = string.Format("When using {0} in a LINQ string comparison the value being compared to must serialize as a string.", methodName);
throw new ArgumentException(message);
}
}
private IMongoQuery BuildStringQuery(MethodCallExpression methodCallExpression)
{
if (methodCallExpression.Method.DeclaringType != typeof(string))
{
return null;
}
var arguments = methodCallExpression.Arguments.ToArray();
if (arguments.Length != 1)
{
return null;
}
var stringExpression = methodCallExpression.Object;
var constantExpression = arguments[0] as ConstantExpression;
if (constantExpression == null)
{
return null;
}
var pattern = Regex.Escape((string)constantExpression.Value);
switch (methodCallExpression.Method.Name)
{
case "Contains": pattern = ".*" + pattern + ".*"; break;
case "EndsWith": pattern = ".*" + pattern; break;
case "StartsWith": pattern = pattern + ".*"; break; // query optimizer will use index for rooted regular expressions
default: return null;
}
var caseInsensitive = false;
MethodCallExpression stringMethodCallExpression;
while ((stringMethodCallExpression = stringExpression as MethodCallExpression) != null)
{
var trimStart = false;
var trimEnd = false;
Expression trimCharsExpression = null;
switch (stringMethodCallExpression.Method.Name)
{
case "ToLower":
caseInsensitive = true;
break;
case "ToUpper":
caseInsensitive = true;
break;
case "Trim":
trimStart = true;
trimEnd = true;
trimCharsExpression = stringMethodCallExpression.Arguments.FirstOrDefault();
break;
case "TrimEnd":
trimEnd = true;
trimCharsExpression = stringMethodCallExpression.Arguments.First();
break;
case "TrimStart":
trimStart = true;
trimCharsExpression = stringMethodCallExpression.Arguments.First();
break;
default:
return null;
}
if (trimStart || trimEnd)
{
var trimCharsPattern = GetTrimCharsPattern(trimCharsExpression);
if (trimCharsPattern == null)
{
return null;
}
if (trimStart)
{
pattern = trimCharsPattern + pattern;
}
if (trimEnd)
{
pattern = pattern + trimCharsPattern;
}
}
stringExpression = stringMethodCallExpression.Object;
}
pattern = "^" + pattern + "$";
if (pattern.StartsWith("^.*"))
{
pattern = pattern.Substring(3);
}
if (pattern.EndsWith(".*$"))
{
pattern = pattern.Substring(0, pattern.Length - 3);
}
var serializationInfo = _serializationInfoHelper.GetSerializationInfo(stringExpression);
var options = caseInsensitive ? "is" : "s";
return Query.Matches(serializationInfo.ElementName, new BsonRegularExpression(pattern, options));
}
private IMongoQuery BuildTypeComparisonQuery(Expression variableExpression, ExpressionType operatorType, ConstantExpression constantExpression)
{
if (operatorType != ExpressionType.Equal)
{
// TODO: support NotEqual?
return null;
}
if (constantExpression.Type != typeof(Type))
{
return null;
}
var actualType = (Type)constantExpression.Value;
var methodCallExpression = variableExpression as MethodCallExpression;
if (methodCallExpression == null)
{
return null;
}
if (methodCallExpression.Method.Name != "GetType" || methodCallExpression.Object == null)
{
return null;
}
if (methodCallExpression.Arguments.Count != 0)
{
return null;
}
var serializationInfo = _serializationInfoHelper.GetSerializationInfo(methodCallExpression.Object);
var nominalType = serializationInfo.NominalType;
var discriminatorConvention = BsonSerializer.LookupDiscriminatorConvention(nominalType);
var discriminator = discriminatorConvention.GetDiscriminator(nominalType, actualType);
if (discriminator == null)
{
return BuildBooleanQuery(true);
}
var elementName = discriminatorConvention.ElementName;
if (serializationInfo.ElementName != null)
{
elementName = string.Format("{0}.{1}", serializationInfo.ElementName, elementName);
}
if (discriminator.IsBsonArray)
{
var discriminatorArray = discriminator.AsBsonArray;
var queries = new IMongoQuery[discriminatorArray.Count + 1];
queries[0] = Query.Size(elementName, discriminatorArray.Count);
for (var i = 0; i < discriminatorArray.Count; i++)
{
queries[i + 1] = Query.EQ(string.Format("{0}.{1}", elementName, i), discriminatorArray[i]);
}
return Query.And(queries);
}
else
{
return Query.And(
Query.NotExists(elementName + ".0"), // trick to check that element is not an array
Query.EQ(elementName, discriminator));
}
}
private IMongoQuery BuildTypeIsQuery(TypeBinaryExpression typeBinaryExpression)
{
var nominalType = typeBinaryExpression.Expression.Type;
var actualType = typeBinaryExpression.TypeOperand;
var discriminatorConvention = BsonSerializer.LookupDiscriminatorConvention(nominalType);
var discriminator = discriminatorConvention.GetDiscriminator(nominalType, actualType);
if (discriminator == null)
{
return BuildBooleanQuery(true);
}
if (discriminator.IsBsonArray)
{
discriminator = discriminator.AsBsonArray[discriminator.AsBsonArray.Count - 1];
}
var elementName = discriminatorConvention.ElementName;
var serializationInfo = _serializationInfoHelper.GetSerializationInfo(typeBinaryExpression.Expression);
if (serializationInfo.ElementName != null)
{
elementName = string.Format("{0}.{1}", serializationInfo.ElementName, elementName);
}
return Query.EQ(elementName, discriminator);
}
private string GetTrimCharsPattern(Expression trimCharsExpression)
{
if (trimCharsExpression == null)
{
return "\\s*";
}
var constantExpression = trimCharsExpression as ConstantExpression;
if (constantExpression == null || !constantExpression.Type.IsArray || constantExpression.Type.GetElementType() != typeof(char))
{
return null;
}
var trimChars = (char[])constantExpression.Value;
if (trimChars.Length == 0)
{
return "\\s*";
}
// build a pattern that matches the characters to be trimmed
var characterClass = string.Join("", trimChars.Select(c => (c == '-') ? "\\-" : (c == ']') ? "\\]" : Regex.Escape(c.ToString())).ToArray());
if (trimChars.Length > 1)
{
characterClass = "[" + characterClass + "]";
}
return characterClass + "*";
}
private int ToInt32(Expression expression)
{
if (expression.Type != typeof(int))
{
throw new ArgumentOutOfRangeException("expression", "Expected an Expression of Type Int32.");
}
var constantExpression = expression as ConstantExpression;
if (constantExpression == null)
{
throw new ArgumentOutOfRangeException("expression", "Expected a ConstantExpression.");
}
return (int)constantExpression.Value;
}
private long ToInt64(Expression expression)
{
if (expression.Type != typeof(int) && expression.Type != typeof(long))
{
throw new ArgumentOutOfRangeException("expression", "Expected an Expression of Type Int32 or Int64.");
}
var constantExpression = expression as ConstantExpression;
if (constantExpression == null)
{
throw new ArgumentOutOfRangeException("expression", "Expected a ConstantExpression.");
}
if (expression.Type == typeof(int))
{
return (long)(int)constantExpression.Value;
}
else
{
return (long)constantExpression.Value;
}
}
}
}