/src/System.Web.Http.OData/OData/Query/Expressions/FilterBinder.cs
C# | 1357 lines | 1043 code | 230 blank | 84 comment | 274 complexity | d70cce01dfceda3e3087e70ec8ebf260 MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
- using System.Collections.Generic;
- using System.Data.Linq;
- using System.Diagnostics.CodeAnalysis;
- using System.Diagnostics.Contracts;
- using System.Globalization;
- using System.Linq;
- using System.Linq.Expressions;
- using System.Reflection;
- using System.Web.Http.Dispatcher;
- using System.Web.Http.OData.Formatter;
- using System.Web.Http.OData.Properties;
- using System.Xml.Linq;
- using Microsoft.Data.Edm;
- using Microsoft.Data.OData;
- using Microsoft.Data.OData.Query;
- using Microsoft.Data.OData.Query.SemanticAst;
- namespace System.Web.Http.OData.Query.Expressions
- {
- /// <summary>
- /// Translates an OData $filter parse tree represented by <see cref="FilterClause"/> to
- /// an <see cref="Expression"/> and applies it to an <see cref="IQueryable"/>.
- /// </summary>
- [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Relies on many ODataLib classes.")]
- internal class FilterBinder
- {
- private const string ODataItParameterName = "$it";
- private static readonly MethodInfo _stringCompareMethodInfo = typeof(string).GetMethod("Compare", new[] { typeof(string), typeof(string), typeof(StringComparison) });
- private static readonly Expression _nullConstant = Expression.Constant(null);
- private static readonly Expression _falseConstant = Expression.Constant(false);
- private static readonly Expression _trueConstant = Expression.Constant(true);
- private static readonly Expression _zeroConstant = Expression.Constant(0);
- private static readonly Expression _ordinalStringComparisonConstant = Expression.Constant(StringComparison.Ordinal);
- private static Dictionary<BinaryOperatorKind, ExpressionType> _binaryOperatorMapping = new Dictionary<BinaryOperatorKind, ExpressionType>
- {
- { BinaryOperatorKind.Add, ExpressionType.Add },
- { BinaryOperatorKind.And, ExpressionType.AndAlso },
- { BinaryOperatorKind.Divide, ExpressionType.Divide },
- { BinaryOperatorKind.Equal, ExpressionType.Equal },
- { BinaryOperatorKind.GreaterThan, ExpressionType.GreaterThan },
- { BinaryOperatorKind.GreaterThanOrEqual, ExpressionType.GreaterThanOrEqual },
- { BinaryOperatorKind.LessThan, ExpressionType.LessThan },
- { BinaryOperatorKind.LessThanOrEqual, ExpressionType.LessThanOrEqual },
- { BinaryOperatorKind.Modulo, ExpressionType.Modulo },
- { BinaryOperatorKind.Multiply, ExpressionType.Multiply },
- { BinaryOperatorKind.NotEqual, ExpressionType.NotEqual },
- { BinaryOperatorKind.Or, ExpressionType.OrElse },
- { BinaryOperatorKind.Subtract, ExpressionType.Subtract },
- };
- private IEdmModel _model;
- private Stack<Dictionary<string, ParameterExpression>> _parametersStack;
- private Dictionary<string, ParameterExpression> _lambdaParameters;
- private ODataQuerySettings _querySettings;
- private IAssembliesResolver _assembliesResolver;
- private FilterBinder(IEdmModel model, IAssembliesResolver assembliesResolver, ODataQuerySettings querySettings)
- {
- Contract.Assert(model != null);
- Contract.Assert(assembliesResolver != null);
- Contract.Assert(querySettings != null);
- Contract.Assert(querySettings.HandleNullPropagation != HandleNullPropagationOption.Default);
- _querySettings = querySettings;
- _parametersStack = new Stack<Dictionary<string, ParameterExpression>>();
- _model = model;
- _assembliesResolver = assembliesResolver;
- }
- public static Expression<Func<TEntityType, bool>> Bind<TEntityType>(FilterClause filterClause, IEdmModel model, IAssembliesResolver assembliesResolver, ODataQuerySettings querySettings)
- {
- return Bind(filterClause, typeof(TEntityType), model, assembliesResolver, querySettings) as Expression<Func<TEntityType, bool>>;
- }
- public static Expression Bind(FilterClause filterClause, Type filterType, IEdmModel model, IAssembliesResolver assembliesResolver, ODataQuerySettings querySettings)
- {
- if (filterClause == null)
- {
- throw Error.ArgumentNull("filterNode");
- }
- if (filterType == null)
- {
- throw Error.ArgumentNull("filterType");
- }
- if (model == null)
- {
- throw Error.ArgumentNull("model");
- }
- if (assembliesResolver == null)
- {
- throw Error.ArgumentNull("assembliesResolver");
- }
- FilterBinder binder = new FilterBinder(model, assembliesResolver, querySettings);
- Expression filter = binder.BindFilterClause(filterClause, filterType);
- Type expectedFilterType = typeof(Func<,>).MakeGenericType(filterType, typeof(bool));
- if (filter.Type != expectedFilterType)
- {
- throw Error.Argument("filterType", SRResources.CannotCastFilter, filter.Type.FullName, expectedFilterType.FullName);
- }
- return filter;
- }
- private Expression Bind(QueryNode node)
- {
- // Recursion guard to avoid stack overflows
- EnsureStackHelper.EnsureStack();
- CollectionNode collectionNode = node as CollectionNode;
- SingleValueNode singleValueNode = node as SingleValueNode;
- if (collectionNode != null)
- {
- switch (node.Kind)
- {
- case QueryNodeKind.CollectionNavigationNode:
- CollectionNavigationNode navigationNode = node as CollectionNavigationNode;
- return BindNavigationPropertyNode(navigationNode.Source, navigationNode.NavigationProperty);
- case QueryNodeKind.CollectionPropertyAccess:
- return BindCollectionPropertyAccessNode(node as CollectionPropertyAccessNode);
- case QueryNodeKind.EntityCollectionCast:
- return BindEntityCollectionCastNode(node as EntityCollectionCastNode);
- default:
- throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, node.Kind, typeof(FilterBinder).Name);
- }
- }
- else if (singleValueNode != null)
- {
- switch (node.Kind)
- {
- case QueryNodeKind.BinaryOperator:
- return BindBinaryOperatorNode(node as BinaryOperatorNode);
- case QueryNodeKind.Constant:
- return BindConstantNode(node as ConstantNode);
- case QueryNodeKind.Convert:
- return BindConvertNode(node as ConvertNode);
- case QueryNodeKind.EntityRangeVariableReference:
- return BindRangeVariable((node as EntityRangeVariableReferenceNode).RangeVariable);
- case QueryNodeKind.NonentityRangeVariableReference:
- return BindRangeVariable((node as NonentityRangeVariableReferenceNode).RangeVariable);
- case QueryNodeKind.SingleValuePropertyAccess:
- return BindPropertyAccessQueryNode(node as SingleValuePropertyAccessNode);
- case QueryNodeKind.UnaryOperator:
- return BindUnaryOperatorNode(node as UnaryOperatorNode);
- case QueryNodeKind.SingleValueFunctionCall:
- return BindSingleValueFunctionCallNode(node as SingleValueFunctionCallNode);
- case QueryNodeKind.SingleNavigationNode:
- SingleNavigationNode navigationNode = node as SingleNavigationNode;
- return BindNavigationPropertyNode(navigationNode.Source, navigationNode.NavigationProperty);
- case QueryNodeKind.Any:
- return BindAnyNode(node as AnyNode);
- case QueryNodeKind.All:
- return BindAllNode(node as AllNode);
- case QueryNodeKind.SingleEntityCast:
- return BindSingleEntityCastNode(node as SingleEntityCastNode);
- default:
- throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, node.Kind, typeof(FilterBinder).Name);
- }
- }
- else
- {
- throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, node.Kind, typeof(FilterBinder).Name);
- }
- }
- private Expression BindSingleEntityCastNode(SingleEntityCastNode node)
- {
- IEdmEntityTypeReference entity = node.EntityTypeReference;
- Contract.Assert(entity != null, "NS casts can contain only entity types");
- Type clrType = EdmLibHelpers.GetClrType(entity, _model);
- Expression source = BindCastSourceNode(node.Source);
- return Expression.TypeAs(source, clrType);
- }
- private Expression BindEntityCollectionCastNode(EntityCollectionCastNode node)
- {
- IEdmEntityTypeReference entity = node.EntityItemType;
- Contract.Assert(entity != null, "NS casts can contain only entity types");
- Type clrType = EdmLibHelpers.GetClrType(entity, _model);
- Expression source = BindCastSourceNode(node.Source);
- return OfType(source, clrType);
- }
- private Expression BindCastSourceNode(QueryNode sourceNode)
- {
- Expression source;
- if (sourceNode == null)
- {
- // if the cast is on the root i.e $it (~/Products?$filter=NS.PopularProducts/.....),
- // source would be null. So bind null to '$it'.
- source = _lambdaParameters[ODataItParameterName];
- }
- else
- {
- source = Bind(sourceNode);
- }
- return source;
- }
- private static Expression OfType(Expression source, Type elementType)
- {
- Contract.Assert(source != null);
- Contract.Assert(elementType != null);
- if (IsIQueryable(source.Type))
- {
- return Expression.Call(null, ExpressionHelperMethods.QueryableOfType.MakeGenericMethod(elementType), source);
- }
- else
- {
- return Expression.Call(null, ExpressionHelperMethods.EnumerableOfType.MakeGenericMethod(elementType), source);
- }
- }
- private Expression BindNavigationPropertyNode(QueryNode sourceNode, IEdmNavigationProperty navigationProperty)
- {
- Expression source;
- // TODO: bug in uri parser is causing this property to be null for the root property.
- if (sourceNode == null)
- {
- source = _lambdaParameters[ODataItParameterName];
- }
- else
- {
- source = Bind(sourceNode);
- }
- return CreatePropertyAccessExpression(source, navigationProperty.Name);
- }
- private Expression BindBinaryOperatorNode(BinaryOperatorNode binaryOperatorNode)
- {
- Expression left = Bind(binaryOperatorNode.Left);
- Expression right = Bind(binaryOperatorNode.Right);
- // handle null propagation only if either of the operands can be null
- bool isNullPropagationRequired = _querySettings.HandleNullPropagation == HandleNullPropagationOption.True && (IsNullable(left.Type) || IsNullable(right.Type));
- if (isNullPropagationRequired)
- {
- // |----------------------------------------------------------------|
- // |SQL 3VL truth table. |
- // |----------------------------------------------------------------|
- // |p | q | p OR q | p AND q | p = q |
- // |----------------------------------------------------------------|
- // |True | True | True | True | True |
- // |True | False | True | False | False |
- // |True | NULL | True | NULL | NULL |
- // |False | True | True | False | False |
- // |False | False | False | False | True |
- // |False | NULL | NULL | False | NULL |
- // |NULL | True | True | NULL | NULL |
- // |NULL | False | NULL | False | NULL |
- // |NULL | NULL | Null | NULL | NULL |
- // |--------|-----------|---------------|---------------|-----------|
- // before we start with null propagation, convert the operators to nullable if already not.
- left = ToNullable(left);
- right = ToNullable(right);
- bool liftToNull = true;
- if (left == _nullConstant || right == _nullConstant)
- {
- liftToNull = false;
- }
- // Expression trees do a very good job of handling the 3VL truth table if we pass liftToNull true.
- return CreateBinaryExpression(binaryOperatorNode.OperatorKind, left, right, liftToNull: liftToNull);
- }
- else
- {
- return CreateBinaryExpression(binaryOperatorNode.OperatorKind, left, right, liftToNull: false);
- }
- }
- private Expression BindConstantNode(ConstantNode constantNode)
- {
- Contract.Assert(constantNode != null);
- // no need to parameterize null's as there cannot be multiple values for null.
- if (constantNode.Value == null)
- {
- return _nullConstant;
- }
- Type constantType = EdmLibHelpers.GetClrType(constantNode.TypeReference, _model, _assembliesResolver);
- if (_querySettings.EnableConstantParameterization)
- {
- return LinqParameterContainer.Parameterize(constantType, constantNode.Value);
- }
- else
- {
- return Expression.Constant(constantNode.Value, constantType);
- }
- }
- private Expression BindConvertNode(ConvertNode convertNode)
- {
- Contract.Assert(convertNode != null);
- Contract.Assert(convertNode.TypeReference != null);
- Expression source = Bind(convertNode.Source);
- Type conversionType = EdmLibHelpers.GetClrType(convertNode.TypeReference, _model, _assembliesResolver);
- if (conversionType == typeof(bool?) && source.Type == typeof(bool))
- {
- // we handle null propagation ourselves. So, if converting from bool to Nullable<bool> ignore.
- return source;
- }
- else if (source == _nullConstant)
- {
- return source;
- }
- else
- {
- Type sourceUnderlyingType = Nullable.GetUnderlyingType(source.Type) ?? source.Type;
- if (sourceUnderlyingType.IsEnum)
- {
- // we handle enum conversions ourselves
- return source;
- }
- else
- {
- // if a cast is from Nullable<T> to Non-Nullable<T> we need to check if source is null
- if (_querySettings.HandleNullPropagation == HandleNullPropagationOption.True
- && IsNullable(source.Type) && !IsNullable(conversionType))
- {
- // source == null ? null : source.Value
- return
- Expression.Condition(
- test: CheckForNull(source),
- ifTrue: Expression.Constant(null, ToNullable(conversionType)),
- ifFalse: Expression.Convert(ExtractValueFromNullableExpression(source), ToNullable(conversionType)));
- }
- else
- {
- return Expression.Convert(source, conversionType);
- }
- }
- }
- }
- private Expression BindFilterClause(FilterClause filterClause, Type filterType)
- {
- ParameterExpression filterParameter = Expression.Parameter(filterType, filterClause.RangeVariable.Name);
- _lambdaParameters = new Dictionary<string, ParameterExpression>();
- _lambdaParameters.Add(filterClause.RangeVariable.Name, filterParameter);
- Expression body = Bind(filterClause.Expression);
- body = ApplyNullPropagationForFilterBody(body);
- Expression filterExpression = Expression.Lambda(body, filterParameter);
- if (_parametersStack.Count != 0)
- {
- _lambdaParameters = _parametersStack.Pop();
- }
- else
- {
- _lambdaParameters = null;
- }
- return filterExpression;
- }
- private Expression ApplyNullPropagationForFilterBody(Expression body)
- {
- if (IsNullable(body.Type))
- {
- if (_querySettings.HandleNullPropagation == HandleNullPropagationOption.True)
- {
- // handle null as false
- // body => body == true. passing liftToNull:false would convert null to false.
- body = Expression.Equal(body, Expression.Constant(true, typeof(bool?)), liftToNull: false, method: null);
- }
- else
- {
- body = Expression.Convert(body, typeof(bool));
- }
- }
- return body;
- }
- private Expression BindRangeVariable(RangeVariable rangeVariable)
- {
- ParameterExpression parameter = _lambdaParameters[rangeVariable.Name];
- return ConvertNonStandardPrimitives(parameter);
- }
- private Expression BindCollectionPropertyAccessNode(CollectionPropertyAccessNode propertyAccessNode)
- {
- Expression source = Bind(propertyAccessNode.Source);
- return CreatePropertyAccessExpression(source, propertyAccessNode.Property.Name);
- }
- private Expression BindPropertyAccessQueryNode(SingleValuePropertyAccessNode propertyAccessNode)
- {
- Expression source = Bind(propertyAccessNode.Source);
- return CreatePropertyAccessExpression(source, propertyAccessNode.Property.Name);
- }
- private Expression CreatePropertyAccessExpression(Expression source, string propertyName)
- {
- if (_querySettings.HandleNullPropagation == HandleNullPropagationOption.True && IsNullable(source.Type) && source != _lambdaParameters[ODataItParameterName])
- {
- Expression propertyAccessExpression = Expression.Property(RemoveInnerNullPropagation(source), propertyName);
- // source.property => source == null ? null : [CastToNullable]RemoveInnerNullPropagation(source).property
- // Notice that we are checking if source is null already. so we can safely remove any null checks when doing source.Property
- Expression ifFalse = ToNullable(ConvertNonStandardPrimitives(propertyAccessExpression));
- return
- Expression.Condition(
- test: Expression.Equal(source, _nullConstant),
- ifTrue: Expression.Constant(null, ifFalse.Type),
- ifFalse: ifFalse);
- }
- else
- {
- return ConvertNonStandardPrimitives(Expression.Property(source, propertyName));
- }
- }
- private Expression BindUnaryOperatorNode(UnaryOperatorNode unaryOperatorNode)
- {
- // No need to handle null-propagation here as CLR already handles it.
- // !(null) = null
- // -(null) = null
- Expression inner = Bind(unaryOperatorNode.Operand);
- switch (unaryOperatorNode.OperatorKind)
- {
- case UnaryOperatorKind.Negate:
- return Expression.Negate(inner);
- case UnaryOperatorKind.Not:
- return Expression.Not(inner);
- default:
- throw Error.NotSupported(SRResources.QueryNodeBindingNotSupported, unaryOperatorNode.Kind, typeof(FilterBinder).Name);
- }
- }
- private Expression BindSingleValueFunctionCallNode(SingleValueFunctionCallNode node)
- {
- switch (node.Name)
- {
- case ClrCanonicalFunctions.StartswithFunctionName:
- return BindStartsWith(node);
- case ClrCanonicalFunctions.EndswithFunctionName:
- return BindEndsWith(node);
- case ClrCanonicalFunctions.SubstringofFunctionName:
- return BindSubstringOf(node);
- case ClrCanonicalFunctions.SubstringFunctionName:
- return BindSubstring(node);
- case ClrCanonicalFunctions.LengthFunctionName:
- return BindLength(node);
- case ClrCanonicalFunctions.IndexofFunctionName:
- return BindIndexOf(node);
- case ClrCanonicalFunctions.TolowerFunctionName:
- return BindToLower(node);
- case ClrCanonicalFunctions.ToupperFunctionName:
- return BindToUpper(node);
- case ClrCanonicalFunctions.TrimFunctionName:
- return BindTrim(node);
- case ClrCanonicalFunctions.ConcatFunctionName:
- return BindConcat(node);
- case ClrCanonicalFunctions.YearFunctionName:
- case ClrCanonicalFunctions.MonthFunctionName:
- case ClrCanonicalFunctions.DayFunctionName:
- case ClrCanonicalFunctions.HourFunctionName:
- case ClrCanonicalFunctions.MinuteFunctionName:
- case ClrCanonicalFunctions.SecondFunctionName:
- return BindDateOrDateTimeOffsetProperty(node);
- case ClrCanonicalFunctions.YearsFunctionName:
- case ClrCanonicalFunctions.MonthsFunctionName:
- case ClrCanonicalFunctions.DaysFunctionName:
- case ClrCanonicalFunctions.HoursFunctionName:
- case ClrCanonicalFunctions.MinutesFunctionName:
- case ClrCanonicalFunctions.SecondsFunctionName:
- return BindTimeSpanProperty(node);
- case ClrCanonicalFunctions.RoundFunctionName:
- return BindRound(node);
- case ClrCanonicalFunctions.FloorFunctionName:
- return BindFloor(node);
- case ClrCanonicalFunctions.CeilingFunctionName:
- return BindCeiling(node);
- default:
- throw new NotImplementedException(Error.Format(SRResources.ODataFunctionNotSupported, node.Name));
- }
- }
- private Expression CreateFunctionCallWithNullPropagation(Expression functionCall, Expression[] arguments)
- {
- if (_querySettings.HandleNullPropagation == HandleNullPropagationOption.True)
- {
- Expression test = CheckIfArgumentsAreNull(arguments);
- if (test == _falseConstant)
- {
- // none of the arguments are/can be null.
- // so no need to do any null propagation
- return functionCall;
- }
- else
- {
- // if one of the arguments is null, result is null (not defined)
- return
- Expression.Condition(
- test: test,
- ifTrue: Expression.Constant(null, ToNullable(functionCall.Type)),
- ifFalse: ToNullable(functionCall));
- }
- }
- else
- {
- return functionCall;
- }
- }
- // we don't have to do null checks inside the function for arguments as we do the null checks before calling
- // the function when null propagation is enabled.
- // this method converts back "arg == null ? null : convert(arg)" to "arg"
- // Also, note that we can do this generically only because none of the odata functions that we support can take null
- // as an argument.
- private Expression RemoveInnerNullPropagation(Expression expression)
- {
- Contract.Assert(expression != null);
- if (_querySettings.HandleNullPropagation == HandleNullPropagationOption.True)
- {
- // only null propagation generates conditional expressions
- if (expression.NodeType == ExpressionType.Conditional)
- {
- expression = (expression as ConditionalExpression).IfFalse;
- Contract.Assert(expression != null);
- if (expression.NodeType == ExpressionType.Convert)
- {
- UnaryExpression unaryExpression = expression as UnaryExpression;
- Contract.Assert(unaryExpression != null);
- if (Nullable.GetUnderlyingType(unaryExpression.Type) == unaryExpression.Operand.Type)
- {
- // this is a cast from T to Nullable<T> which is redundant.
- expression = unaryExpression.Operand;
- }
- }
- }
- }
- return expression;
- }
- // creates an expression for the corresponding OData function.
- private Expression MakeFunctionCall(MemberInfo member, params Expression[] arguments)
- {
- Contract.Assert(member.MemberType == MemberTypes.Property || member.MemberType == MemberTypes.Method);
- IEnumerable<Expression> functionCallArguments = arguments;
- if (_querySettings.HandleNullPropagation == HandleNullPropagationOption.True)
- {
- // we don't have to check if the argument is null inside the function call as we do it already
- // before calling the function. So remove the redunadant null checks.
- functionCallArguments = arguments.Select(a => RemoveInnerNullPropagation(a));
- }
- // if the argument is of type Nullable<T>, then translate the argument to Nullable<T>.Value as none
- // of the cannonical functions have overloads for Nullable<> arguments.
- functionCallArguments = ExtractValueFromNullableArguments(functionCallArguments);
- Expression functionCall;
- if (member.MemberType == MemberTypes.Method)
- {
- MethodInfo method = member as MethodInfo;
- if (method.IsStatic)
- {
- functionCall = Expression.Call(null, method, functionCallArguments);
- }
- else
- {
- functionCall = Expression.Call(functionCallArguments.First(), method, functionCallArguments.Skip(1));
- }
- }
- else
- {
- // property
- functionCall = Expression.Property(functionCallArguments.First(), member as PropertyInfo);
- }
- return CreateFunctionCallWithNullPropagation(functionCall, arguments);
- }
- private Expression BindCeiling(SingleValueFunctionCallNode node)
- {
- Contract.Assert("ceiling" == node.Name);
- Expression[] arguments = BindArguments(node.Arguments);
- Contract.Assert(arguments.Length == 1 && IsDoubleOrDecimal(arguments[0].Type));
- MethodInfo ceiling = arguments[0].Type == typeof(double) ? ClrCanonicalFunctions.CeilingOfDouble : ClrCanonicalFunctions.CeilingOfDecimal;
- return MakeFunctionCall(ceiling, arguments);
- }
- private Expression BindFloor(SingleValueFunctionCallNode node)
- {
- Contract.Assert("floor" == node.Name);
- Expression[] arguments = BindArguments(node.Arguments);
- Contract.Assert(arguments.Length == 1 && IsDoubleOrDecimal(arguments[0].Type));
- MethodInfo floor = arguments[0].Type == typeof(double) ? ClrCanonicalFunctions.FloorOfDouble : ClrCanonicalFunctions.FloorOfDecimal;
- return MakeFunctionCall(floor, arguments);
- }
- private Expression BindRound(SingleValueFunctionCallNode node)
- {
- Contract.Assert("round" == node.Name);
- Expression[] arguments = BindArguments(node.Arguments);
- Contract.Assert(arguments.Length == 1 && IsDoubleOrDecimal(arguments[0].Type));
- MethodInfo round = arguments[0].Type == typeof(double) ? ClrCanonicalFunctions.RoundOfDouble : ClrCanonicalFunctions.RoundOfDecimal;
- return MakeFunctionCall(round, arguments);
- }
- private Expression BindDateOrDateTimeOffsetProperty(SingleValueFunctionCallNode node)
- {
- Expression[] arguments = BindArguments(node.Arguments);
- Contract.Assert(arguments.Length == 1 && IsDateOrOffset(arguments[0].Type));
- PropertyInfo property;
- if (IsDate(arguments[0].Type))
- {
- Contract.Assert(ClrCanonicalFunctions.DateProperties.ContainsKey(node.Name));
- property = ClrCanonicalFunctions.DateProperties[node.Name];
- }
- else
- {
- Contract.Assert(ClrCanonicalFunctions.DateTimeOffsetProperties.ContainsKey(node.Name));
- property = ClrCanonicalFunctions.DateTimeOffsetProperties[node.Name];
- }
- return MakeFunctionCall(property, arguments);
- }
- private Expression BindTimeSpanProperty(SingleValueFunctionCallNode node)
- {
- Expression[] arguments = BindArguments(node.Arguments);
- Contract.Assert(arguments.Length == 1 && IsDateOrOffset(arguments[0].Type));
- Contract.Assert(ClrCanonicalFunctions.TimeSpanProperties.ContainsKey(node.Name));
- return MakeFunctionCall(ClrCanonicalFunctions.TimeSpanProperties[node.Name], arguments);
- }
- private Expression BindConcat(SingleValueFunctionCallNode node)
- {
- Contract.Assert("concat" == node.Name);
- Expression[] arguments = BindArguments(node.Arguments);
- ValidateAllStringArguments(node.Name, arguments);
- Contract.Assert(arguments.Length == 2 && arguments[0].Type == typeof(string) && arguments[1].Type == typeof(string));
- return MakeFunctionCall(ClrCanonicalFunctions.Concat, arguments);
- }
- private Expression BindTrim(SingleValueFunctionCallNode node)
- {
- Contract.Assert("trim" == node.Name);
- Expression[] arguments = BindArguments(node.Arguments);
- ValidateAllStringArguments(node.Name, arguments);
- Contract.Assert(arguments.Length == 1 && arguments[0].Type == typeof(string));
- return MakeFunctionCall(ClrCanonicalFunctions.Trim, arguments);
- }
- private Expression BindToUpper(SingleValueFunctionCallNode node)
- {
- Contract.Assert("toupper" == node.Name);
- Expression[] arguments = BindArguments(node.Arguments);
- ValidateAllStringArguments(node.Name, arguments);
- Contract.Assert(arguments.Length == 1 && arguments[0].Type == typeof(string));
- return MakeFunctionCall(ClrCanonicalFunctions.ToUpper, arguments);
- }
- private Expression BindToLower(SingleValueFunctionCallNode node)
- {
- Contract.Assert("tolower" == node.Name);
- Expression[] arguments = BindArguments(node.Arguments);
- ValidateAllStringArguments(node.Name, arguments);
- Contract.Assert(arguments.Length == 1 && arguments[0].Type == typeof(string));
- return MakeFunctionCall(ClrCanonicalFunctions.ToLower, arguments);
- }
- private Expression BindIndexOf(SingleValueFunctionCallNode node)
- {
- Contract.Assert("indexof" == node.Name);
- Expression[] arguments = BindArguments(node.Arguments);
- ValidateAllStringArguments(node.Name, arguments);
- Contract.Assert(arguments.Length == 2 && arguments[0].Type == typeof(string) && arguments[1].Type == typeof(string));
- return MakeFunctionCall(ClrCanonicalFunctions.IndexOf, arguments);
- }
- private Expression BindSubstring(SingleValueFunctionCallNode node)
- {
- Contract.Assert("substring" == node.Name);
- Expression[] arguments = BindArguments(node.Arguments);
- if (arguments[0].Type != typeof(string))
- {
- throw new ODataException(Error.Format(SRResources.FunctionNotSupportedOnEnum, node.Name));
- }
- Expression functionCall;
- if (arguments.Length == 2)
- {
- Contract.Assert(IsInteger(arguments[1].Type));
- // When null propagation is allowed, we use a safe version of String.Substring(int).
- // But for providers that would not recognize custom expressions like this, we map
- // directly to String.Substring(int)
- if (_querySettings.HandleNullPropagation == HandleNullPropagationOption.True)
- {
- // Safe function is static and takes string "this" as first argument
- functionCall = MakeFunctionCall(ClrCanonicalFunctions.SubstringStartNoThrow, arguments);
- }
- else
- {
- functionCall = MakeFunctionCall(ClrCanonicalFunctions.SubstringStart, arguments);
- }
- }
- else
- {
- // arguments.Length == 3 implies String.Substring(int, int)
- Contract.Assert(arguments.Length == 3 && IsInteger(arguments[1].Type) && IsInteger(arguments[2].Type));
- // When null propagation is allowed, we use a safe version of String.Substring(int, int).
- // But for providers that would not recognize custom expressions like this, we map
- // directly to String.Substring(int, int)
- if (_querySettings.HandleNullPropagation == HandleNullPropagationOption.True)
- {
- // Safe function is static and takes string "this" as first argument
- functionCall = MakeFunctionCall(ClrCanonicalFunctions.SubstringStartAndLengthNoThrow, arguments);
- }
- else
- {
- functionCall = MakeFunctionCall(ClrCanonicalFunctions.SubstringStartAndLength, arguments);
- }
- }
- return functionCall;
- }
- private Expression BindLength(SingleValueFunctionCallNode node)
- {
- Contract.Assert("length" == node.Name);
- Expression[] arguments = BindArguments(node.Arguments);
- ValidateAllStringArguments(node.Name, arguments);
- Contract.Assert(arguments.Length == 1 && arguments[0].Type == typeof(string));
- return MakeFunctionCall(ClrCanonicalFunctions.Length, arguments);
- }
- private Expression BindSubstringOf(SingleValueFunctionCallNode node)
- {
- Contract.Assert("substringof" == node.Name);
- Expression[] arguments = BindArguments(node.Arguments);
- ValidateAllStringArguments(node.Name, arguments);
- Contract.Assert(arguments.Length == 2 && arguments[0].Type == typeof(string) && arguments[1].Type == typeof(string));
- // NOTE: this is reversed because it is reverse in WCF DS and in the OData spec
- return MakeFunctionCall(ClrCanonicalFunctions.Contains, arguments[1], arguments[0]);
- }
- private Expression BindStartsWith(SingleValueFunctionCallNode node)
- {
- Contract.Assert("startswith" == node.Name);
- Expression[] arguments = BindArguments(node.Arguments);
- ValidateAllStringArguments(node.Name, arguments);
- Contract.Assert(arguments.Length == 2 && arguments[0].Type == typeof(string) && arguments[1].Type == typeof(string));
- return MakeFunctionCall(ClrCanonicalFunctions.StartsWith, arguments);
- }
- private Expression BindEndsWith(SingleValueFunctionCallNode node)
- {
- Contract.Assert("endswith" == node.Name);
- Expression[] arguments = BindArguments(node.Arguments);
- ValidateAllStringArguments(node.Name, arguments);
- Contract.Assert(arguments.Length == 2 && arguments[0].Type == typeof(string) && arguments[1].Type == typeof(string));
- return MakeFunctionCall(ClrCanonicalFunctions.EndsWith, arguments);
- }
- private Expression[] BindArguments(IEnumerable<QueryNode> nodes)
- {
- return nodes.OfType<SingleValueNode>().Select(n => Bind(n)).ToArray();
- }
- private static void ValidateAllStringArguments(string functionName, Expression[] arguments)
- {
- if (arguments.Any(arg => arg.Type != typeof(string)))
- {
- throw new ODataException(Error.Format(SRResources.FunctionNotSupportedOnEnum, functionName));
- }
- }
- private Expression BindAllNode(AllNode allNode)
- {
- ParameterExpression allIt = HandleLambdaParameters(allNode.RangeVariables);
- Expression source;
- Contract.Assert(allNode.Source != null);
- source = Bind(allNode.Source);
- Expression body = source;
- Contract.Assert(allNode.Body != null);
- body = Bind(allNode.Body);
- body = ApplyNullPropagationForFilterBody(body);
- body = Expression.Lambda(body, allIt);
- Expression all = All(source, body);
- if (_querySettings.HandleNullPropagation == HandleNullPropagationOption.True && IsNullable(source.Type))
- {
- // IFF(source == null) null; else Any(body);
- all = ToNullable(all);
- return Expression.Condition(
- test: Expression.Equal(source, _nullConstant),
- ifTrue: Expression.Constant(null, all.Type),
- ifFalse: all);
- }
- else
- {
- return all;
- }
- }
- private Expression BindAnyNode(AnyNode anyNode)
- {
- ParameterExpression anyIt = HandleLambdaParameters(anyNode.RangeVariables);
- Expression source;
- Contract.Assert(anyNode.Source != null);
- source = Bind(anyNode.Source);
- Expression body = null;
- // uri parser places an Constant node with value true for empty any() body
- if (anyNode.Body != null && anyNode.Body.Kind != QueryNodeKind.Constant)
- {
- body = Bind(anyNode.Body);
- body = ApplyNullPropagationForFilterBody(body);
- body = Expression.Lambda(body, anyIt);
- }
- Expression any = Any(source, body);
- if (_querySettings.HandleNullPropagation == HandleNullPropagationOption.True && IsNullable(source.Type))
- {
- // IFF(source == null) null; else Any(body);
- any = ToNullable(any);
- return Expression.Condition(
- test: Expression.Equal(source, _nullConstant),
- ifTrue: Expression.Constant(null, any.Type),
- ifFalse: any);
- }
- else
- {
- return any;
- }
- }
- private ParameterExpression HandleLambdaParameters(IEnumerable<RangeVariable> rangeVariables)
- {
- ParameterExpression lambdaIt = null;
- Contract.Assert(_lambdaParameters != null);
- _parametersStack.Push(_lambdaParameters);
- Dictionary<string, ParameterExpression> newParameters = new Dictionary<string, ParameterExpression>();
- foreach (RangeVariable rangeVariable in rangeVariables)
- {
- ParameterExpression parameter;
- if (!_lambdaParameters.TryGetValue(rangeVariable.Name, out parameter))
- {
- // Work-around issue 481323 where UriParser yields a collection parameter type
- // for primitive collections rather than the inner element type of the collection.
- // Remove this block of code when 481323 is resolved.
- IEdmTypeReference edmTypeReference = rangeVariable.TypeReference;
- IEdmCollectionTypeReference collectionTypeReference = edmTypeReference as IEdmCollectionTypeReference;
- if (collectionTypeReference != null)
- {
- IEdmCollectionType collectionType = collectionTypeReference.Definition as IEdmCollectionType;
- if (collectionType != null)
- {
- edmTypeReference = collectionType.ElementType;
- }
- }
- parameter = Expression.Parameter(EdmLibHelpers.GetClrType(edmTypeReference, _model, _assembliesResolver), rangeVariable.Name);
- Contract.Assert(lambdaIt == null, "There can be only one parameter in an Any/All lambda");
- lambdaIt = parameter;
- }
- newParameters.Add(rangeVariable.Name, parameter);
- }
- _lambdaParameters = newParameters;
- return lambdaIt;
- }
- // If the expression is of non-standard edm primitive type (like uint), convert the expression to its standard edm type.
- // Also, note that only expressions generated for ushort, uint and ulong can be understood by linq2sql and EF.
- // The rest (char, char[], Binary) would cause issues with linq2sql and EF.
- private Expression ConvertNonStandardPrimitives(Expression source)
- {
- bool isNonstandardEdmPrimitive;
- Type conversionType = EdmLibHelpers.IsNonstandardEdmPrimitive(source.Type, out isNonstandardEdmPrimitive);
- if (isNonstandardEdmPrimitive)
- {
- Type sourceType = source.Type;
- sourceType = Nullable.GetUnderlyingType(sourceType) ?? sourceType;
- Contract.Assert(sourceType != conversionType);
- Expression convertedExpression = null;
- if (sourceType.IsEnum)
- {
- // we handle enum conversions ourselves
- convertedExpression = source;
- }
- else
- {
- switch (Type.GetTypeCode(sourceType))
- {
- case TypeCode.UInt16:
- case TypeCode.UInt32:
- case TypeCode.UInt64:
- convertedExpression = Expression.Convert(ExtractValueFromNullableExpression(source), conversionType);
- break;
- case TypeCode.Char:
- convertedExpression = Expression.Call(ExtractValueFromNullableExpression(source), "ToString", typeArguments: null, arguments: null);
- break;
- case TypeCode.Object:
- if (sourceType == typeof(char[]))
- {
- convertedExpression = Expression.New(typeof(string).GetConstructor(new[] { typeof(char[]) }), source);
- }
- else if (sourceType == typeof(XElement))
- {
- convertedExpression = Expression.Call(source, "ToString", typeArguments: null, arguments: null);
- }
- else if (sourceType == typeof(Binary))
- {
- convertedExpression = Expression.Call(source, "ToArray", typeArguments: null, arguments: null);
- }
- break;
- default:
- Contract.Assert(false, Error.Format("missing non-standard type support for {0}", sourceType.Name));
- break;
- }
- }
- if (_querySettings.HandleNullPropagation == HandleNullPropagationOption.True && IsNullable(source.Type))
- {
- // source == null ? null : source
- return Expression.Condition(
- CheckForNull(source),
- ifTrue: Expression.Constant(null, ToNullable(convertedExpression.Type)),
- ifFalse: ToNullable(convertedExpression));
- }
- else
- {
- return convertedExpression;
- }
- }
- return source;
- }
- private static Expression CreateBinaryExpression(BinaryOperatorKind binaryOperator, Expression left, Expression right, bool liftToNull)
- {
- ExpressionType binaryExpressionType;
- // When comparing an enum to a string, parse the string, convert both to the enum underlying type, and compare the values
- // When comparing an enum to an enum, convert both to the underlying type, and compare the values
- Type leftUnderlyingType = Nullable.GetUnderlyingType(left.Type) ?? left.Type;
- Type rightUnderlyingType = Nullable.GetUnderlyingType(right.Type) ?? right.Type;
- if (leftUnderlyingType.IsEnum || rightUnderlyingType.IsEnum)
- {
- Type enumType = leftUnderlyingType.IsEnum ? leftUnderlyingType : rightUnderlyingType;
- Type enumUnderlyingType = Enum.GetUnderlyingType(enumType);
- left = ConvertToEnumUnderlyingType(left, enumType, enumUnderlyingType);
- right = ConvertToEnumUnderlyingType(right, enumType, enumUnderlyingType);
- }
- if (left.Type != right.Type)
- {
- // one of them must be nullable and the other is not.
- left = ToNullable(left);
- right = ToNullable(right);
- }
- if (left.Type == typeof(string) || right.Type == typeof(string))
- {
- // convert nulls of type object to nulls of type string to make the String.Compare call work
- left = ConvertNull(left, typeof(string));
- right = ConvertNull(right, typeof(string));
- // Use string.Compare instead of comparison for gt, ge, lt, le between two strings since direct comparisons are not supported
- switch (binaryOperator)
- {
- case BinaryOperatorKind.GreaterThan:
- case BinaryOperatorKind.GreaterThanOrEqual:
- case BinaryOperatorKind.LessThan:
- case BinaryOperatorKind.LessThanOrEqual:
- left = Expression.Call(_stringCompareMethodInfo, left, right, _ordinalStringComparisonConstant);
- right = _zeroConstant;
- break;
- default:
- break;
- }
- }
- if (_binaryOperatorMapping.TryGetValue(binaryOperator, out binaryExpressionType))
- {
- if (left.Type == typeof(byte[]) || right.Type == typeof(byte[]))
- {
- left = ConvertNull(left, typeof(byte[]));
- right = ConvertNull(right, typeof(byte[]));
- switch (binaryExpressionType)
- {
- case ExpressionType.Equal:
- return Expression.MakeBinary(binaryExpressionType, left, right, liftToNull, method: Linq2ObjectsComparisonMethods.AreByteArraysEqualMethodInfo);
- case ExpressionType.NotEqual:
- return Expression.MakeBinary(binaryExpressionType, left, right, liftToNull, method: Linq2ObjectsComparisonMethods.AreByteArraysNotEqualMethodInfo);
- default:
- IEdmPrimitiveType binaryType = EdmLibHelpers.GetEdmPrimitiveTypeOrNull(typeof(byte[]));
- throw new ODataException(Error.Format(SRResources.BinaryOperatorNotSupported, binaryType.FullName(), binaryType.FullName(), binaryOperator));
- }
- }
- else
- {
- return Expression.MakeBinary(binaryExpressionType, left, right, liftToNull, method: null);
- }
- }
- else
- …
Large files files are truncated, but you can click here to view the full file