Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / ndp / fx / src / DataWeb / Server / System / Data / Services / Parsing / RequestQueryParser.cs / 1 / RequestQueryParser.cs
//---------------------------------------------------------------------- //// Copyright (c) Microsoft Corporation. All rights reserved. // //// Provides a type to parse expressions in request queries. // // // @owner [....] //--------------------------------------------------------------------- namespace System.Data.Services.Parsing { #region Namespaces. using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Data.Services.Parsing; using System.Data.Services.Providers; #if ASTORIA_OPEN_OBJECT using System.Data.Services.OpenTypes; #endif #endregion Namespaces. ////// This class provides static methods to parse query options and compose /// them on an existing query. /// internal static class RequestQueryParser { #region Fields. ///Constant for "null" literal. internal static readonly ConstantExpression NullLiteral = Expression.Constant(null); #endregion Fields. #region Internal methods. ///Gets a type for /// Type to base resulting type on. ///that allows null values. /// internal static Type GetTypeAllowingNull(Type type) { Debug.Assert(type != null, "type != null"); return TypeAllowsNull(type) ? type : typeof(Nullable<>).MakeGenericType(type); } ///if it's a reference or Nullable<> type; /// Nullable< > otherwise. /// Checks whether the specified type is a generic nullable type. /// Type to check. ///true if internal static bool IsNullableType(Type type) { return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); } ///is nullable; false otherwise. Checks whether /// Expression to check. ///is a null constant. true if internal static bool IsNullConstant(Expression expression) { Debug.Assert(expression != null, "expression != null"); return expression == NullLiteral || (expression.NodeType == ExpressionType.Constant && ((ConstantExpression)expression).Value == null); } ///is a null constant; false otherwise. Checks whether the specified /// Type to check. ///can be assigned null. true if type is a reference type or a Nullable type; false otherwise. internal static bool TypeAllowsNull(Type type) { Debug.Assert(type != null, "type != null"); return !type.IsValueType || IsNullableType(type); } ///Sorts a query like a SQL ORDER BY clause does. /// Service with data and configuration. /// Original source for query. /// Ordering definition to compose. ///The composed query. internal static IQueryable OrderBy(IDataService service, IQueryable source, string ordering) { Debug.Assert(service != null, "service != null"); Debug.Assert(source != null, "source != null"); Debug.Assert(ordering != null, "ordering != null"); ParameterExpression parameterForIt = Expression.Parameter(source.ElementType, "it"); ExpressionParser parser = new ExpressionParser(service, parameterForIt, ordering); IEnumerableorderings = parser.ParseOrdering(); Expression queryExpr = source.Expression; string methodAsc = "OrderBy"; string methodDesc = "OrderByDescending"; foreach (DynamicOrdering o in orderings) { Type selectorType = o.Selector.Type; if (!service.Provider.GetTypeIsOrdered(selectorType)) { string resourceTypeName = WebUtil.GetTypeName(service.Provider, o.Selector.Type); throw DataServiceException.CreateBadRequestError(Strings.RequestQueryParser_OrderByDoesNotSupportType(resourceTypeName)); } queryExpr = Expression.Call( typeof(Queryable), o.Ascending ? methodAsc : methodDesc, new Type[] { source.ElementType, selectorType }, queryExpr, Expression.Quote(Expression.Lambda(o.Selector, parameterForIt))); methodAsc = "ThenBy"; methodDesc = "ThenByDescending"; } return source.Provider.CreateQuery(queryExpr); } /// Filters a query like a SQL WHERE clause does. /// Service with data and configuration. /// Original source for query. /// Predicate to compose. ///The composed query. internal static IQueryable Where(IDataService service, IQueryable source, string predicate) { Debug.Assert(service != null, "service != null"); Debug.Assert(service != null, "source != null"); Debug.Assert(predicate != null, "predicate != null"); LambdaExpression lambda = ParseLambdaForWhere(service, source.ElementType, predicate); ////Trace.WriteLine("predicate=" + predicate + "; lambda=" + lambda.ToString()); return source.Provider.CreateQuery( Expression.Call(typeof(Queryable), "Where", new Type[] { source.ElementType }, source.Expression, Expression.Quote(lambda))); } #endregion Internal methods. #region Private methods. ///Parses a lambda expression. /// Service with data and configuration. /// Type for "it" contextual variable. /// Expression to parse. ///The parsed expression. private static LambdaExpression ParseLambdaForWhere(IDataService service, Type typeForIt, string expression) { Debug.Assert(service != null, "service != null"); Debug.Assert(typeForIt != null, "typeForIt != null"); Debug.Assert(expression != null, "expression != null"); ParameterExpression parameterForIt = Expression.Parameter(typeForIt, "it"); ExpressionParser parser = new ExpressionParser(service, parameterForIt, expression); return Expression.Lambda(parser.ParseWhere(), parameterForIt); } #endregion Private methods. ///Use this class to define ordering for resources. private class DynamicOrdering { #region Private fields. ///Whether the order is ascending. private readonly bool ascending; ///Selector expression in ordering. private readonly Expression selector; #endregion Private fields. ///Initializes a new /// Selector expression in ordering. /// Whether the order is ascending. internal DynamicOrdering(Expression selector, bool ascending) { this.selector = selector; this.ascending = ascending; } #region Properties. ///instance. Whether the order is ascending. internal bool Ascending { get { return this.ascending; } } ///Selector expression in ordering. internal Expression Selector { get { return this.selector; } } #endregion Properties. } ///Use this class to parse an expression in the Astoria URI format. [DebuggerDisplay("ExpressionParser ({lexer.text})")] private class ExpressionParser { #region Fields. ///Maximum recursion limit on deserializer. private const int RecursionLimit = 100; ///A type that is not numeric. private const int NumericTypeNotNumeric = 0; ///A type that is a char, single, double or decimal. private const int NumericTypeNotIntegral = 1; ///A type that is a signed integral. private const int NumericTypeSignedIntegral = 2; ///A type that is an unsigned integral. private const int NumericTypeUnsignedIntegral = 3; ///Empty Expressions array. private static readonly Expression[] emptyExpressions = new Expression[0]; ///Constant for "true" literal. private static readonly ConstantExpression trueLiteral = Expression.Constant(true); ///Constant for "false" literal. private static readonly ConstantExpression falseLiteral = Expression.Constant(false); ///Dictionary of system functions. private static readonly Dictionaryfunctions = FunctionDescription.CreateFunctions(); /// Provider of data and metadata. private readonly IDataServiceProvider provider; ///Service with data and configuration. private readonly IDataService service; ///Literals. private Dictionaryliterals; /// "it" contextual parameter. private ParameterExpression it; ///Expression lexer. private ExpressionLexer lexer; ///Whether the expression tree should propagate nulls explicitly. private bool nullPropagationRequired; ///Depth of recursion. private int recursionDepth; #endregion Fields. #region Constructors. ///Initializes a new /// Service with data and configuration. /// Parameters for the current "it" context. /// Expression to parse. internal ExpressionParser(IDataService service, ParameterExpression parameterForIt, string expression) { Debug.Assert(service != null, "service != null"); Debug.Assert(expression != null, "expression != null"); Debug.Assert(parameterForIt != null, "parameterForIt != null"); this.service = service; this.provider = service.Provider; this.nullPropagationRequired = this.provider.NullPropagationRequired; this.literals = new Dictionary. (); this.it = parameterForIt; this.lexer = new ExpressionLexer(expression); } #endregion Constructors. /// Current token being processed. private Token CurrentToken { get { return this.lexer.CurrentToken; } set { this.lexer.CurrentToken = value; } } ///Parses the text expression for a .Where method invocation. ///The parsed expression. internal Expression ParseWhere() { int exprPos = this.lexer.Position; Expression expr = this.ParseExpression(); #if ASTORIA_OPEN_OBJECT if (IsOpenPropertyExpression(expr)) { expr = Expression.Convert(expr, typeof(bool)); } else #endif if (IsNullConstant(expr)) { expr = falseLiteral; } else if (expr.Type == typeof(bool?)) { Expression test = Expression.Equal(expr, Expression.Constant(null, typeof(bool?))); expr = Expression.Condition(test, falseLiteral, Expression.Property(expr, "Value")); } else if (expr.Type != typeof(bool)) { throw ParseError(Strings.RequestQueryParser_ExpressionTypeMismatch(this.GetTypeName(typeof(bool)), exprPos)); } this.lexer.ValidateToken(TokenId.End); Debug.Assert(expr != null, "expr != null"); Debug.Assert(expr.Type == typeof(bool), "expr.Type(" + expr.Type + ") == typeof(bool)"); return expr; } ///Parses the text expression for ordering. ///An enumeration of orderings. internal IEnumerableParseOrdering() { List orderings = new List (); while (true) { Expression expr = this.ParseExpression(); bool ascending = true; if (this.TokenIdentifierIs(ExpressionConstants.KeywordAscending)) { this.lexer.NextToken(); } else if (this.TokenIdentifierIs(ExpressionConstants.KeywordDescending)) { this.lexer.NextToken(); ascending = false; } orderings.Add(new DynamicOrdering(expr, ascending)); if (this.CurrentToken.Id != TokenId.Comma) { break; } this.lexer.NextToken(); } this.ValidateToken(TokenId.End); return orderings; } /// Compares two byte arrays for equality. /// First byte array.Second byte array. ///true if the arrays are equal; false otherwise. private static bool ByteArraysEqual(byte[] b0, byte[] b1) { if (b0 == b1) { return true; } if (b0 == null || b1 == null) { return false; } if (b0.Length != b1.Length) { return false; } for (int i = 0; i < b0.Length; i++) { if (b0[i] != b1[i]) { return false; } } return true; } ///Compares two byte arrays for equality. /// First byte array.Second byte array. ///true if the arrays are not equal; false otherwise. private static bool ByteArraysNotEqual(byte[] b0, byte[] b1) { return !ByteArraysEqual(b0, b1); } ///Gets a non-nullable version of the specified type. /// Type to get non-nullable version for. ////// private static Type GetNonNullableType(Type type) { return Nullable.GetUnderlyingType(type) ?? type; } ///if type is a reference type or a /// non-nullable type; otherwise, the underlying value type. /// Checks whether the specified type is a signed integral type. /// Type to check. ///true if private static bool IsSignedIntegralType(Type type) { return GetNumericTypeKind(type) == NumericTypeSignedIntegral; } ///is a signed integral type; false otherwise. Checks whether the specified type is an unsigned integral type. /// Type to check. ///true if private static bool IsUnsignedIntegralType(Type type) { return GetNumericTypeKind(type) == NumericTypeUnsignedIntegral; } ///is an unsigned integral type; false otherwise. Gets a flag for the numeric kind of type. /// Type to get numeric kind for. ////// One of NumericTypeNotNumeric; NumericTypeNotIntegral if it's char, /// single, double or decimal; NumericTypeSignedIntegral, or NumericTypeUnsignedIntegral. /// private static int GetNumericTypeKind(Type type) { type = GetNonNullableType(type); Debug.Assert(!type.IsEnum, "!type.IsEnum"); switch (Type.GetTypeCode(type)) { case TypeCode.Char: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return NumericTypeNotIntegral; case TypeCode.SByte: case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: return NumericTypeSignedIntegral; case TypeCode.Byte: return NumericTypeUnsignedIntegral; default: return NumericTypeNotNumeric; } } ///Checks whether type is a (possibly nullable) enumeration type. /// Type to check. ///true if type is an enumeration or a nullable enumeration; false otherwise. private static bool IsEnumType(Type type) { return GetNonNullableType(type).IsEnum; } ///Returns an object that can enumerate the specified type and its supertypes. /// Type to based enumeration on. ///An object that can enumerate the specified type and its supertypes. private static IEnumerableSelfAndBaseTypes(Type type) { if (type.IsInterface) { List types = new List (); AddInterface(types, type); return types; } return SelfAndBaseClasses(type); } /// Returns an object that can enumerate the specified type and its supertypes. /// Type to based enumeration on. ///An object that can enumerate the specified type and its supertypes. private static IEnumerableSelfAndBaseClasses(Type type) { while (type != null) { yield return type; type = type.BaseType; } } /// Adds an interface type to a list of types, including inherited interfaces. /// Types list ot add to. /// Interface type to add. private static void AddInterface(Listtypes, Type type) { if (!types.Contains(type)) { types.Add(type); foreach (Type t in type.GetInterfaces()) { AddInterface(types, t); } } } /// Finds the best applicable methods from the specified array that match the arguments. /// Candidate methods. /// Argument expressions. ///Best applicable methods. private static MethodData[] FindBestApplicableMethods(MethodData[] applicable, Expression[] args) { Debug.Assert(applicable != null, "applicable != null"); Listresult = new List (); foreach (MethodData method in applicable) { bool betterThanAllOthers = true; foreach (MethodData otherMethod in applicable) { if (otherMethod != method && IsBetterThan(args, otherMethod, method)) { betterThanAllOthers = false; break; } } if (betterThanAllOthers) { result.Add(method); } } return result.ToArray(); } /// Parses the specified text into a number. /// Text to parse. /// Type to parse into. ///The parsed number. private static object ParseNumber(string text, Type type) { TypeCode tc = Type.GetTypeCode(GetNonNullableType(type)); switch (tc) { case TypeCode.SByte: sbyte sb; if (sbyte.TryParse(text, out sb)) { return sb; } break; case TypeCode.Byte: byte b; if (byte.TryParse(text, out b)) { return b; } break; case TypeCode.Int16: short s; if (short.TryParse(text, out s)) { return s; } break; case TypeCode.Int32: int i; if (int.TryParse(text, out i)) { return i; } break; case TypeCode.Int64: long l; if (long.TryParse(text, out l)) { return l; } break; case TypeCode.Single: float f; if (float.TryParse(text, out f)) { return f; } break; case TypeCode.Double: double d; if (double.TryParse(text, out d)) { return d; } break; case TypeCode.Decimal: decimal e; if (decimal.TryParse(text, out e)) { return e; } break; } return null; } ///Checks whether the source type is compatible with the value type. /// Source type. /// Target type. ///true if source can be used in place of target; false otherwise. private static bool IsCompatibleWith(Type source, Type target) { if (source == target) { return true; } if (!target.IsValueType) { return target.IsAssignableFrom(source); } Type sourceType = GetNonNullableType(source); Type targetType = GetNonNullableType(target); //// This rule stops the parser from considering nullable types as incompatible //// with non-nullable types. We have however implemented this rule because we just //// access underlying rules. C# requires an explicit .Value access, and EDM has no //// nullablity on types and (at the model level) implements null propagation. //// //// if (sourceType != source && targetType == target) //// { //// return false; //// } TypeCode sourceCode = sourceType.IsEnum ? TypeCode.Object : Type.GetTypeCode(sourceType); TypeCode targetCode = targetType.IsEnum ? TypeCode.Object : Type.GetTypeCode(targetType); switch (sourceCode) { case TypeCode.SByte: switch (targetCode) { case TypeCode.SByte: case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return true; } break; case TypeCode.Byte: switch (targetCode) { case TypeCode.Byte: case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return true; } break; case TypeCode.Int16: switch (targetCode) { case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return true; } break; case TypeCode.Int32: switch (targetCode) { case TypeCode.Int32: case TypeCode.Int64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return true; } break; case TypeCode.Int64: switch (targetCode) { case TypeCode.Int64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return true; } break; case TypeCode.Single: switch (targetCode) { case TypeCode.Single: case TypeCode.Double: return true; } break; default: if (sourceType == targetType) { return true; } break; } // Anything can be converted to something that's *exactly* an object. #if ASTORIA_OPEN_OBJECT if (target == typeof(object)) { return true; } #endif return false; } ////// Checks whether one type list is a better fit than other for the /// specified expressions. /// /// Expressions for arguments. /// First type list to check. /// Second type list to check. ////// true if private static bool IsBetterThan(Expression[] args, IEnumerablehas better parameter matching than . /// firstCandidate, IEnumerable secondCandidate) { bool better = false; using (IEnumerator first = firstCandidate.GetEnumerator()) using (IEnumerator second = secondCandidate.GetEnumerator()) { for (int i = 0; i < args.Length; i++) { first.MoveNext(); second.MoveNext(); int c = CompareConversions(args[i].Type, first.Current, second.Current); if (c < 0) { return false; } else if (c > 0) { better = true; } } } return better; } /// /// Checks whether one method is a better fit than other for the /// specified expressions. /// /// Expressions for arguments. /// First method to check. /// Second method to check. ////// true if private static bool IsBetterThan(Expression[] args, MethodData m1, MethodData m2) { Debug.Assert(args != null, "args != null"); Debug.Assert(m1 != null, "m1 != null"); Debug.Assert(m2 != null, "m2 != null"); return IsBetterThan(args, m1.ParameterTypes, m2.ParameterTypes); } ///has better parameter matching than . /// Checks which conversion is better. /// Source type. /// First candidate type to convert to. /// Second candidate type to convert to. ////// Return 1 if s -> t1 is a better conversion than s -> t2 /// Return -1 if s -> t2 is a better conversion than s -> t1 /// Return 0 if neither conversion is better /// private static int CompareConversions(Type source, Type targetA, Type targetB) { // If both types are exactly the same, there is no preference. if (targetA == targetB) { return 0; } // Look for exact matches. if (source == targetA) { return 1; } else if (source == targetB) { return -1; } // If one is compatible and the other is not, choose the compatible type. bool compatibleT1AndT2 = IsCompatibleWith(targetA, targetB); bool compatibleT2AndT1 = IsCompatibleWith(targetB, targetA); if (compatibleT1AndT2 && !compatibleT2AndT1) { return 1; } else if (compatibleT2AndT1 && !compatibleT1AndT2) { return -1; } // Prefer to keep the original nullability. bool sourceNullable = IsNullableType(source); bool typeNullableA = IsNullableType(targetA); bool typeNullableB = IsNullableType(targetB); if (sourceNullable == typeNullableA && sourceNullable != typeNullableB) { return 1; } else if (sourceNullable != typeNullableA && sourceNullable == typeNullableB) { return -1; } // Prefer signed to unsigned. if (IsSignedIntegralType(targetA) && IsUnsignedIntegralType(targetB)) { return 1; } else if (IsSignedIntegralType(targetB) && IsUnsignedIntegralType(targetA)) { return -1; } // Prefer non-object to object. if (targetA != typeof(object) && targetB == typeof(object)) { return 1; } else if (targetB != typeof(object) && targetA == typeof(object)) { return -1; } return 0; } ///Generates an Equal expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateEqual(Expression left, Expression right) { #if ASTORIA_OPEN_OBJECT if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return LateBoundMethods.EqualExpression(left, right); } #endif if (left.Type == typeof(byte[])) { MethodInfo byteArrayCompareMethod = typeof(ExpressionParser).GetMethod("ByteArraysEqual", BindingFlags.NonPublic | BindingFlags.Static); return Expression.Equal(left, right, false, byteArrayCompareMethod); } return Expression.Equal(left, right); } ///Generates a NotEqual expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateNotEqual(Expression left, Expression right) { #if ASTORIA_OPEN_OBJECT if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return LateBoundMethods.NotEqualExpression(left, right); } #endif if (left.Type == typeof(byte[])) { MethodInfo byteArrayCompareMethod = typeof(ExpressionParser).GetMethod("ByteArraysNotEqual", BindingFlags.NonPublic | BindingFlags.Static); return Expression.NotEqual(left, right, false, byteArrayCompareMethod); } return Expression.NotEqual(left, right); } ///Generates a GreaterThan comparison expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateGreaterThan(Expression left, Expression right) { #if ASTORIA_OPEN_OBJECT if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return LateBoundMethods.GreaterThanExpression(left, right); } #endif if (left.Type == typeof(string)) { return Expression.GreaterThan(left, right, false, GetStringCompareMethod("StringGreaterThanMethod")); } return Expression.GreaterThan(left, right); } ///Generates a GreaterThanOrEqual comparsion expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateGreaterThanEqual(Expression left, Expression right) { #if ASTORIA_OPEN_OBJECT if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return LateBoundMethods.GreaterThanOrEqualExpression(left, right); } #endif if (left.Type == typeof(string)) { return Expression.GreaterThanOrEqual(left, right, false, GetStringCompareMethod("StringGreaterThanOrEqualMethod")); } return Expression.GreaterThanOrEqual(left, right); } ///Generates a LessThan comparsion expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateLessThan(Expression left, Expression right) { #if ASTORIA_OPEN_OBJECT if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return LateBoundMethods.LessThanExpression(left, right); } #endif if (left.Type == typeof(string)) { return Expression.LessThan(left, right, false, GetStringCompareMethod("StringLessThanMethod")); } return Expression.LessThan(left, right); } ///Generates a LessThanOrEqual comparsion expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateLessThanEqual(Expression left, Expression right) { #if ASTORIA_OPEN_OBJECT if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return LateBoundMethods.LessThanOrEqualExpression(left, right); } #endif if (left.Type == typeof(string)) { return Expression.LessThanOrEqual(left, right, false, GetStringCompareMethod("StringLessThanOrEqualMethod")); } return Expression.LessThanOrEqual(left, right); } ///Generates an addition expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateAdd(Expression left, Expression right) { #if ASTORIA_OPEN_OBJECT if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return LateBoundMethods.AddExpression(left, right); } #endif return Expression.Add(left, right); } ///Generates a subtract expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateSubtract(Expression left, Expression right) { #if ASTORIA_OPEN_OBJECT if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return LateBoundMethods.SubtractExpression(left, right); } #endif return Expression.Subtract(left, right); } ///Gets a static method to compare strings. /// Name of method. ///The private static MethodInfo GetStringCompareMethod(string methodName) { Debug.Assert(methodName != null, "methodName != null"); MethodInfo result = typeof(ExpressionParser).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static); Debug.Assert(result != null, "result != null"); return result; } #if ASTORIA_OPEN_OBJECT ///for the method. /// Checks whether the specified /// Non-nullis part of an open property expression. /// to check. /// true if private static bool IsOpenPropertyExpression(Expression expression) { Debug.Assert(expression != null, "expression != null"); return expression != NullLiteral && expression.Type == typeof(object); } #endif ///is based on an open property; false otherwise. Checks whether the left string is less than or equal to the right string. /// Left value.Right value. ///true if the left string is less than or equal to the right string. private static bool StringLessThanOrEqualMethod(string left, string right) { return string.CompareOrdinal(left, right) <= 0; } ///Checks whether the left string is less than the right string. /// Left value.Right value. ///true if the left string is less than the right string. private static bool StringLessThanMethod(string left, string right) { return string.CompareOrdinal(left, right) < 0; } ///Checks whether the left string is greater than or equal to the right string. /// Left value.Right value. ///true if the left string is greater than or equal to the right string. private static bool StringGreaterThanOrEqualMethod(string left, string right) { return string.CompareOrdinal(left, right) >= 0; } ///Checks whether the left string is greater than the right string. /// Left value.Right value. ///true if the left string is greater than the right string. private static bool StringGreaterThanMethod(string left, string right) { return string.CompareOrdinal(left, right) > 0; } ///Gets a static method by name. /// Name of method to get. /// Left expression to resolve method from and to use as argument. /// Right expression. ///The method. private static MethodInfo GetStaticMethod(string methodName, Expression left, Expression right) { return left.Type.GetMethod(methodName, new Type[] { left.Type, right.Type }); } ///Generates a static method call. /// Method name. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right) { return Expression.Call(null, GetStaticMethod(methodName, left, right), new Expression[] { left, right }); } ///Creates an exception for a parse error. /// Message text. ///A new Exception. private static Exception ParseError(string message) { return DataServiceException.CreateSyntaxError(message); } ///Checks that the given token has the specified identifier. /// Token to check /// Identifier to check. ///true if private static bool TokenIdentifierIs(Token token, string id) { return token.Id == TokenId.Identifier && String.Equals(id, token.Text, StringComparison.OrdinalIgnoreCase); } #region Parsing. ///is an identifier with the specified text. Handles a ?: operator - not supported. ///The parsed expression. private Expression ParseExpression() { this.RecurseEnter(); Expression expr = this.ParseLogicalOr(); this.RecurseLeave(); return expr; } ///Handles or operator. ///The parsed expression. private Expression ParseLogicalOr() { this.RecurseEnter(); Expression left = this.ParseLogicalAnd(); while (this.TokenIdentifierIs(ExpressionConstants.KeywordOr)) { Token op = this.CurrentToken; this.lexer.NextToken(); Expression right = this.ParseLogicalAnd(); this.CheckAndPromoteOperands(typeof(OperationSignatures.ILogicalSignatures), op.Text, ref left, ref right, op.Position); #if ASTORIA_OPEN_OBJECT if (left.Type == typeof(object) || right.Type == typeof(object)) { left = LateBoundMethods.OrElseExpression(left, right); if (left == null) { throw ParseError(Strings.RequestQueryParser_BooleanExpressionsExpectedFor(op.Text)); } } else #endif { left = Expression.OrElse(left, right); } } this.RecurseLeave(); return left; } ///Handles and operator. ///The parsed expression. private Expression ParseLogicalAnd() { this.RecurseEnter(); Expression left = this.ParseComparison(); while (this.TokenIdentifierIs(ExpressionConstants.KeywordAnd)) { Token op = this.CurrentToken; this.lexer.NextToken(); Expression right = this.ParseComparison(); this.CheckAndPromoteOperands(typeof(OperationSignatures.ILogicalSignatures), op.Text, ref left, ref right, op.Position); #if ASTORIA_OPEN_OBJECT if (left.Type == typeof(object) || right.Type == typeof(object)) { left = LateBoundMethods.AndAlsoExpression(left, right); if (left == null) { throw ParseError(Strings.RequestQueryParser_BooleanExpressionsExpectedFor(op.Text)); } } else #endif { left = Expression.AndAlso(left, right); } } this.RecurseLeave(); return left; } ///Handles eq, ne, lt, gt, le, ge operators. ///The parsed expression. private Expression ParseComparison() { this.RecurseEnter(); Expression left = this.ParseAdditive(); while (this.CurrentToken.IsComparisonOperator) { Token op = this.CurrentToken; this.lexer.NextToken(); Expression right = this.ParseAdditive(); bool equality = op.IsEqualityOperator; if (equality && !left.Type.IsValueType && !right.Type.IsValueType) { if (left.Type != right.Type) { if (IsNullConstant(left)) { left = Expression.Constant(null, right.Type); } else if (IsNullConstant(right)) { right = Expression.Constant(null, left.Type); } else if (left.Type.IsAssignableFrom(right.Type)) { right = Expression.Convert(right, left.Type); } else if (right.Type.IsAssignableFrom(left.Type)) { left = Expression.Convert(left, right.Type); } else { throw this.IncompatibleOperandsError(op.Text, left, right, op.Position); } } } else if (left == NullLiteral || right == NullLiteral) { if (!equality) { throw ParseError( Strings.RequestQueryParser_NullOperatorUnsupported(op.Text, op.Position, this.lexer.ExpressionText)); } // Because we don't have an explicit "is null" check, literal comparisons // to null are special. if (!TypeAllowsNull(left.Type)) { left = Expression.Convert(left, typeof(Nullable<>).MakeGenericType(left.Type)); } else if (!TypeAllowsNull(right.Type)) { right = Expression.Convert(right, typeof(Nullable<>).MakeGenericType(right.Type)); } } else { // Enums should be checked here for promotion when supported, but they aren't in this version. Debug.Assert(!IsEnumType(left.Type), "!IsEnumType(left.Type)"); Debug.Assert(!IsEnumType(right.Type), "!IsEnumType(right.Type)"); Type signatures = equality ? typeof(OperationSignatures.IEqualitySignatures) : typeof(OperationSignatures.IRelationalSignatures); this.CheckAndPromoteOperands(signatures, op.Text, ref left, ref right, op.Position); } Debug.Assert(op.Id == TokenId.Identifier, "op.id == TokenId.Identifier"); switch (op.Text) { case ExpressionConstants.KeywordEqual: left = GenerateEqual(left, right); break; case ExpressionConstants.KeywordNotEqual: left = GenerateNotEqual(left, right); break; case ExpressionConstants.KeywordGreaterThan: left = GenerateGreaterThan(left, right); break; case ExpressionConstants.KeywordGreaterThanOrEqual: left = GenerateGreaterThanEqual(left, right); break; case ExpressionConstants.KeywordLessThan: left = GenerateLessThan(left, right); break; case ExpressionConstants.KeywordLessThanOrEqual: left = GenerateLessThanEqual(left, right); break; } } this.RecurseLeave(); return left; } ///Handles +, -, & operators (& for string concat, not supported). ///The parsed expression. private Expression ParseAdditive() { this.RecurseEnter(); Expression left = this.ParseMultiplicative(); while (this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordAdd) || this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordSub)) { Token op = this.CurrentToken; this.lexer.NextToken(); Expression right = this.ParseMultiplicative(); if (op.IdentifierIs(ExpressionConstants.KeywordAdd)) { this.CheckAndPromoteOperands(typeof(OperationSignatures.IAddSignatures), op.Text, ref left, ref right, op.Position); left = GenerateAdd(left, right); } else { Debug.Assert(ExpressionParser.TokenIdentifierIs(op, ExpressionConstants.KeywordSub), "ExpressionParser.TokenIdentifierIs(op, ExpressionConstants.KeywordSub)"); this.CheckAndPromoteOperands(typeof(OperationSignatures.ISubtractSignatures), op.Text, ref left, ref right, op.Position); left = GenerateSubtract(left, right); } } this.RecurseLeave(); return left; } ///Handles mul, div, mod operators. ///The parsed expression. private Expression ParseMultiplicative() { this.RecurseEnter(); Expression left = this.ParseUnary(); while (this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordMultiply) || this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordDivide) || this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordModulo)) { Token op = this.CurrentToken; this.lexer.NextToken(); Expression right = this.ParseUnary(); this.CheckAndPromoteOperands(typeof(OperationSignatures.IArithmeticSignatures), op.Text, ref left, ref right, op.Position); if (op.IdentifierIs(ExpressionConstants.KeywordMultiply)) { #if ASTORIA_OPEN_OBJECT if (left.Type == typeof(object) || right.Type == typeof(object)) { left = LateBoundMethods.MultiplyExpression(left, right); } else #endif { left = Expression.Multiply(left, right); } } else if (op.IdentifierIs(ExpressionConstants.KeywordDivide)) { #if ASTORIA_OPEN_OBJECT if (left.Type == typeof(object) || right.Type == typeof(object)) { left = LateBoundMethods.DivideExpression(left, right); } else #endif { left = Expression.Divide(left, right); } } else { Debug.Assert(op.IdentifierIs(ExpressionConstants.KeywordModulo), "op.IdentifierIs(ExpressionConstants.KeywordModulo)"); #if ASTORIA_OPEN_OBJECT if (left.Type == typeof(object) || right.Type == typeof(object)) { left = LateBoundMethods.ModuloExpression(left, right); } else #endif { left = Expression.Modulo(left, right); } } } this.RecurseLeave(); return left; } ///Handles -, not unary operators. ///The parsed expression. private Expression ParseUnary() { this.RecurseEnter(); if (this.CurrentToken.Id == TokenId.Minus || this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordNot)) { Token op = this.CurrentToken; this.lexer.NextToken(); if (op.Id == TokenId.Minus && (ExpressionLexer.IsNumeric(this.CurrentToken.Id))) { Token numberLiteral = this.CurrentToken; numberLiteral.Text = "-" + numberLiteral.Text; numberLiteral.Position = op.Position; this.CurrentToken = numberLiteral; this.RecurseLeave(); return this.ParsePrimary(); } Expression expr = this.ParseUnary(); if (op.Id == TokenId.Minus) { this.CheckAndPromoteOperand(typeof(OperationSignatures.INegationSignatures), op.Text, ref expr, op.Position); #if ASTORIA_OPEN_OBJECT if (expr.Type == typeof(object)) { expr = LateBoundMethods.NegateExpression(expr); } else #endif { expr = Expression.Negate(expr); } } else { this.CheckAndPromoteOperand(typeof(OperationSignatures.INotSignatures), op.Text, ref expr, op.Position); #if ASTORIA_OPEN_OBJECT if (expr.Type == typeof(object)) { expr = LateBoundMethods.NotExpression(expr); } else #endif if (expr.Type == typeof(bool) || expr.Type == typeof(Nullable)) { // Expression.Not will take numerics and apply '~' to them, thus the extra check here. expr = Expression.Not(expr); } else { throw ParseError(Strings.RequestQueryParser_NotDoesNotSupportType(expr.Type)); } } this.RecurseLeave(); return expr; } this.RecurseLeave(); return this.ParsePrimary(); } /// Handles primary expressions. ///The parsed expression. private Expression ParsePrimary() { this.RecurseEnter(); Expression expr = this.ParsePrimaryStart(); while (true) { if (this.CurrentToken.Id == TokenId.Slash) { this.lexer.NextToken(); expr = this.ParseMemberAccess(expr); } else { break; } } this.RecurseLeave(); return expr; } ///Handles the start of primary expressions. ///The parsed expression. private Expression ParsePrimaryStart() { switch (this.CurrentToken.Id) { case TokenId.BooleanLiteral: return this.ParseTypedLiteral(typeof(bool), XmlConstants.EdmBooleanTypeName); case TokenId.DateTimeLiteral: return this.ParseTypedLiteral(typeof(DateTime), XmlConstants.EdmDateTimeTypeName); case TokenId.DecimalLiteral: return this.ParseTypedLiteral(typeof(decimal), XmlConstants.EdmDecimalTypeName); case TokenId.NullLiteral: return this.ParseNullLiteral(); case TokenId.Identifier: return this.ParseIdentifier(); case TokenId.StringLiteral: return this.ParseTypedLiteral(typeof(string), XmlConstants.EdmStringTypeName); case TokenId.Int64Literal: return this.ParseTypedLiteral(typeof(Int64), XmlConstants.EdmInt64TypeName); case TokenId.IntegerLiteral: return this.ParseTypedLiteral(typeof(Int32), XmlConstants.EdmInt32TypeName); case TokenId.DoubleLiteral: return this.ParseTypedLiteral(typeof(double), XmlConstants.EdmDoubleTypeName); case TokenId.SingleLiteral: return this.ParseTypedLiteral(typeof(Single), XmlConstants.EdmSingleTypeName); case TokenId.GuidLiteral: return this.ParseTypedLiteral(typeof(Guid), XmlConstants.EdmGuidTypeName); case TokenId.BinaryLiteral: return this.ParseTypedLiteral(typeof(byte[]), XmlConstants.EdmBinaryTypeName); case TokenId.OpenParen: return this.ParseParenExpression(); default: throw ParseError(Strings.RequestQueryParser_ExpressionExpected(this.CurrentToken.Position)); } } ///Handles parenthesized expressions. ///The parsed expression. private Expression ParseParenExpression() { if (this.CurrentToken.Id != TokenId.OpenParen) { throw ParseError(Strings.RequestQueryParser_OpenParenExpected(this.CurrentToken.Position)); } this.lexer.NextToken(); Expression e = this.ParseExpression(); if (this.CurrentToken.Id != TokenId.CloseParen) { throw ParseError(Strings.RequestQueryParser_CloseParenOrOperatorExpected(this.CurrentToken.Position)); } this.lexer.NextToken(); return e; } ///Handles identifiers. ///The parsed expression. private Expression ParseIdentifier() { this.ValidateToken(TokenId.Identifier); bool identifierIsFunction = this.lexer.PeekNextToken().Id == TokenId.OpenParen; if (identifierIsFunction) { return this.ParseIdentifierAsFunction(); } else { return this.ParseMemberAccess(this.it); } } ///Handles identifiers which have been recognized as functions. ///The parsed expression. private Expression ParseIdentifierAsFunction() { FunctionDescription[] functionDescriptions; Token functionToken = this.CurrentToken; if (!functions.TryGetValue(functionToken.Text, out functionDescriptions)) { throw ParseError(Strings.RequestQueryParser_UnknownFunction(functionToken.Text, functionToken.Position)); } this.lexer.NextToken(); Expression[] originalArguments = this.ParseArgumentList(); Expression[] arguments = this.nullPropagationRequired ? originalArguments : (Expression[])originalArguments.Clone(); FunctionDescription function = this.FindBestFunction(functionDescriptions, ref arguments); if (function == null) { string message = Strings.RequestQueryParser_NoApplicableFunction( functionToken.Text, functionToken.Position, FunctionDescription.BuildSignatureList(functionToken.Text, functionDescriptions)); throw ParseError(message); } // Special case for null propagation - we never strip nullability from expressions. if (this.nullPropagationRequired && function.IsTypeCast) { Expression typeExpression = arguments[arguments.Length - 1]; Debug.Assert(typeExpression != null, "typeExpression != null -- otherwise function finding failed."); Debug.Assert(typeExpression.Type == typeof(Type), "typeExpression.Type == typeof(Type) -- last argument is always type."); Type castTargetType = (Type)((ConstantExpression)typeExpression).Value; if (!TypeAllowsNull(castTargetType)) { arguments[arguments.Length - 1] = Expression.Constant(typeof(Nullable<>).MakeGenericType(castTargetType)); } } Expression result = function.ConversionFunction(this.it, arguments); if (this.nullPropagationRequired && !function.IsTypeCheck && !function.IsTypeCast) { Debug.Assert( originalArguments != arguments, "originalArguments != arguments -- arguments should have been cloned"); Debug.Assert( originalArguments.Length == arguments.Length, "originalArguments.Length == arguments.Length -- arguments should not be added/removed"); for (int i = 0; i < originalArguments.Length; i++) { result = this.ConsiderNullPropagation(originalArguments[i], result); } } return result; } ///Handles boolean literals. ///The parsed expression. private Expression ParseBooleanLiteral() { Debug.Assert(this.CurrentToken.Id == TokenId.BooleanLiteral, "this.CurrentToken.Id == TokenId.BooleanLiteral"); string originalText = this.CurrentToken.Text; this.lexer.NextToken(); if (originalText == ExpressionConstants.KeywordTrue) { return trueLiteral; } else { Debug.Assert(originalText == ExpressionConstants.KeywordFalse, "originalText == ExpressionConstants.KeywordFalse"); return falseLiteral; } } ///Handles typed literals. /// Expected type to be parsed. /// Expected type name. ///The constants expression produced by building the given literal. private Expression ParseTypedLiteral(Type targetType, string targetTypeName) { object targetValue; if (!WebConvert.TryKeyStringToPrimitive(this.CurrentToken.Text, targetType, out targetValue)) { string message = Strings.RequestQueryParser_UnrecognizedLiteral(targetTypeName, this.CurrentToken.Text, this.CurrentToken.Position); throw ParseError(message); } Expression result = this.CreateLiteral(targetValue, this.CurrentToken.Text); this.lexer.NextToken(); return result; } ///Handles 'null' literals. ///The parsed expression. private Expression ParseNullLiteral() { Debug.Assert(this.CurrentToken.Id == TokenId.NullLiteral, "this.CurrentToken.Id == TokenId.NullLiteral"); this.lexer.NextToken(); return NullLiteral; } ///Handles member access. /// Instance being accessed. ///The parsed expression. private Expression ParseMemberAccess(Expression instance) { Debug.Assert(instance != null, "instance != null"); Type type = instance.Type; int errorPos = this.lexer.Position; string id = this.CurrentToken.GetIdentifier(); this.lexer.NextToken(); // An open paren here would indicate calling a method in regular C# syntax. ResourceProperty property = this.FindProperty(type, id); if (property != null) { // We have a strongly-type property. Expression result = this.ConsiderNullPropagation(instance, Expression.Property(instance, property.PropertyInfo)); if (property.ResourceContainer != null) { Expression filter = DataServiceConfiguration.ComposeQueryInterceptors(this.service, property.ResourceContainer); if (filter != null) { // We did null propagation for accessing the property, but we may also need // to do null propagation on the property value itself (otherwise the interception // lambda needs to check the argument for null, which is probably unexpected). result = RequestQueryProcessor.ComposePropertyNavigation( result, (LambdaExpression)filter, this.provider.NullPropagationRequired); } } return result; } else { // Open types can have any members inside them #if ASTORIA_OPEN_OBJECT if (type == typeof(object) || OpenTypeAttribute.IsOpenType(type)) { // object LateBoundMethods.GetValue(object, string) Expression call = Expression.Call(null /* instance */, LateBoundMethods.GetValueMethodInfo, instance, Expression.Constant(id)); return this.ConsiderNullPropagation(instance, call); } else #endif { throw ParseError(Strings.RequestQueryParser_UnknownProperty(id, this.GetTypeName(type), errorPos)); } } } ///Handles argument lists. ///The parsed expressions. private Expression[] ParseArgumentList() { if (this.CurrentToken.Id != TokenId.OpenParen) { throw ParseError(Strings.RequestQueryParser_OpenParenExpected(this.CurrentToken.Position)); } this.lexer.NextToken(); Expression[] args = this.CurrentToken.Id != TokenId.CloseParen ? this.ParseArguments() : emptyExpressions; if (this.CurrentToken.Id != TokenId.CloseParen) { throw ParseError(Strings.RequestQueryParser_CloseParenOrCommaExpected(this.CurrentToken.Position)); } this.lexer.NextToken(); return args; } ///Handles comma-separated arguments. ///The parsed expressions. private Expression[] ParseArguments() { ListargList = new List (); while (true) { argList.Add(this.ParseExpression()); if (this.CurrentToken.Id != TokenId.Comma) { break; } this.lexer.NextToken(); } return argList.ToArray(); } #endregion Parsing. /// Creates a constant expression with the specified literal. /// Constant value. /// Value text. ///The created expression. private Expression CreateLiteral(object value, string text) { ConstantExpression expr = Expression.Constant(value); this.literals.Add(expr, text); return expr; } ///Finds the best fitting function for the specified arguments. /// Functions to consider. /// Arguments; if a best function is found, promoted arguments. ///The best fitting function; null if none found or ambiguous. private FunctionDescription FindBestFunction(FunctionDescription[] functions, ref Expression[] arguments) { Debug.Assert(functions != null, "functions != null"); ListapplicableFunctions = new List (functions.Length); List applicablePromotedArguments = new List (functions.Length); // Build a list of applicable functions (and cache their promoted arguments). foreach (FunctionDescription candidate in functions) { if (candidate.ParameterTypes.Length != arguments.Length) { continue; } Expression[] promotedArguments = new Expression[arguments.Length]; bool argumentsMatch = true; for (int i = 0; i < candidate.ParameterTypes.Length; i++) { promotedArguments[i] = this.PromoteExpression(arguments[i], candidate.ParameterTypes[i], true); if (promotedArguments[i] == null) { argumentsMatch = false; break; } } if (argumentsMatch) { applicableFunctions.Add(candidate); applicablePromotedArguments.Add(promotedArguments); } } // Return the best applicable function. if (applicableFunctions.Count == 0) { // No matching function. return null; } else if (applicableFunctions.Count == 1) { arguments = applicablePromotedArguments[0]; return applicableFunctions[0]; } else { // Find a single function which is better than all others. int bestFunctionIndex = -1; for (int i = 0; i < applicableFunctions.Count; i++) { bool betterThanAllOthers = true; for (int j = 0; j < applicableFunctions.Count; j++) { if (i != j && IsBetterThan(arguments, applicableFunctions[j].ParameterTypes, applicableFunctions[i].ParameterTypes)) { betterThanAllOthers = false; break; } } if (betterThanAllOthers) { if (bestFunctionIndex == -1) { bestFunctionIndex = i; } else { // Ambiguous. return null; } } } if (bestFunctionIndex == -1) { return null; } arguments = applicablePromotedArguments[bestFunctionIndex]; return applicableFunctions[bestFunctionIndex]; } } /// Rewrites an expression to propagate null values if necessary. /// Expression to check for null. /// Expression to yield ifdoes not yield null. /// The possibly rewriteen private Expression ConsiderNullPropagation(Expression element, Expression notNullExpression) { if (!this.nullPropagationRequired || element == this.it || !TypeAllowsNull(element.Type)) { return notNullExpression; } // Tiny optimization: remove the check on constants which are known not to be null. // Otherwise every string literal propagates out, which is correct but unnecessarily messy. if (element is ConstantExpression && element != NullLiteral) { return notNullExpression; } // ifTrue and ifFalse expressions must match exactly. We need to ensure that the 'false' // side is nullable, and the 'true' side is a null of the correct type. Expression test = Expression.Equal(element, Expression.Constant(null, element.Type)); Expression falseIf = notNullExpression; if (!TypeAllowsNull(falseIf.Type)) { falseIf = Expression.Convert(falseIf, typeof(Nullable<>).MakeGenericType(falseIf.Type)); } Expression trueIf = Expression.Constant(null, falseIf.Type); return Expression.Condition(test, trueIf, falseIf); } ///. Checks that the operand (possibly promoted) is valid for the specified operation. /// Type with signatures to match. /// Name of operation for error reporting. /// Expression for operand. /// Position for error reporting. private void CheckAndPromoteOperand(Type signatures, string operationName, ref Expression expr, int errorPos) { Debug.Assert(signatures != null, "signatures != null"); Debug.Assert(operationName != null, "operationName != null"); Expression[] args = new Expression[] { expr }; MethodBase method; if (this.FindMethod(signatures, "F", args, out method) != 1) { throw ParseError(Strings.RequestQueryParser_IncompatibleOperand(operationName, this.GetTypeName(args[0].Type), errorPos)); } expr = args[0]; } ///Checks that the operands (possibly promoted) are valid for the specified operation. /// Type with signatures to match. /// Name of operation for error reporting. /// Expression for left operand. /// Expression for right operand. /// Position for error reporting. private void CheckAndPromoteOperands(Type signatures, string operationName, ref Expression left, ref Expression right, int errorPos) { Expression[] args = new Expression[] { left, right }; MethodBase method; if (this.FindMethod(signatures, "F", args, out method) != 1) { throw this.IncompatibleOperandsError(operationName, left, right, errorPos); } left = args[0]; right = args[1]; } ///Finds a property in the specified type with the given name. /// Type in which to look for member. /// Member name. ///The ResourceProperty for the specified type. private ResourceProperty FindProperty(Type type, string memberName) { // This could also be a FindPropertyOrField method. Debug.Assert(type != null, "type != null"); Debug.Assert(memberName != null, "memberName != null"); ResourceProperty property = this.provider.TryResolvePropertyName(type, memberName); if (property != null) { if (property.ResourceContainer != null) { this.service.Configuration.CheckResourceRightsForRead(property.ResourceContainer, true /* singleResult */); } return property; } else { return null; } } ///Finds the named method in the specifid type. /// Type to look in. /// Name of method to look for. /// Arguments to method. /// Best method found. ///Number of matching methods. private int FindMethod(Type type, string methodName, Expression[] args, out MethodBase method) { BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Instance; foreach (Type t in SelfAndBaseTypes(type)) { MemberInfo[] members = t.FindMembers(MemberTypes.Method, flags, Type.FilterName, methodName); int count = this.FindBestMethod(members.Cast(), args, out method); if (count != 0) { return count; } } method = null; return 0; } /// Finds all applicable methods from the specified enumeration that match the arguments. /// Enumerable object of candidate methods. /// Argument expressions. ///Methods that apply to the specified arguments. private MethodData[] FindApplicableMethods(IEnumerablemethods, Expression[] args) { List result = new List (); foreach (MethodBase method in methods) { MethodData methodData = new MethodData(method, method.GetParameters()); if (this.IsApplicable(methodData, args)) { result.Add(methodData); } } return result.ToArray(); } /// Finds the best methods for the specified arguments given a candidate method enumeration. /// Enumerable object for candidate methods. /// Argument expressions to match. /// Best matched method. ///The number of "best match" methods. private int FindBestMethod(IEnumerablemethods, Expression[] args, out MethodBase method) { MethodData[] applicable = this.FindApplicableMethods(methods, args); if (applicable.Length > 1) { applicable = FindBestApplicableMethods(applicable, args); } int result = applicable.Length; method = null; if (applicable.Length == 1) { // If we started off with all non-OpenType expressions and end with all-OpenType // expressions, we've been too aggresive - the transition from non-open-types // to open types should initially happen only as a result of accessing open properties. #if ASTORIA_OPEN_OBJECT MethodData md = applicable[0]; bool originalArgsDefined = true; bool promotedArgsOpen = true; for (int i = 0; i < args.Length; i++) { originalArgsDefined = originalArgsDefined && !IsOpenPropertyExpression(args[i]); promotedArgsOpen = promotedArgsOpen && md.Parameters[i].ParameterType == typeof(object); args[i] = md.Args[i]; } method = (originalArgsDefined && promotedArgsOpen) ? null : md.MethodBase; result = (method == null) ? 0 : 1; #else for (int i = 0; i < args.Length; i++) { args[i] = applicable[0].Args[i]; } method = applicable[0].MethodBase; result = 1; #endif } else if (applicable.Length > 1) { // We may have the case for operators (which C# doesn't) in which we have a nullable operand // and a non-nullable operand. We choose to convert the one non-null operand to nullable in that // case (the binary expression will lift to null). if (args.Length == 2 && applicable.Length == 2 && GetNonNullableType(applicable[0].Parameters[0].ParameterType) == GetNonNullableType(applicable[1].Parameters[0].ParameterType)) { MethodData nullableMethod = TypeAllowsNull(applicable[0].Parameters[0].ParameterType) ? applicable[0] : applicable[1]; args[0] = nullableMethod.Args[0]; args[1] = nullableMethod.Args[1]; return this.FindBestMethod(methods, args, out method); } } return result; } /// Gets a name that can be used for error messages. /// Type to get name for. ///The name of the specified private string GetTypeName(Type type) { Debug.Assert(type != null, "type != null"); return WebUtil.GetTypeName(this.provider, type); } ///. Creates an exception indicated that two operands are incompatible. /// Name of operation for operands. /// Expression for left-hand operand. /// Expression for right-hand operand. /// Position for error. ///A new private Exception IncompatibleOperandsError(string operationName, Expression left, Expression right, int pos) { string message = Strings.RequestQueryParser_IncompatibleOperands( operationName, this.GetTypeName(left.Type), this.GetTypeName(right.Type), pos); return ParseError(message); } ///. Checks whether the specified method is applicable given the argument expressions. /// Method to check. /// Argument expressions. ///true if the method is applicable; false otherwise. private bool IsApplicable(MethodData method, Expression[] args) { if (method.Parameters.Length != args.Length) { return false; } Expression[] promotedArgs = new Expression[args.Length]; for (int i = 0; i < args.Length; i++) { ParameterInfo pi = method.Parameters[i]; Debug.Assert(!pi.IsOut, "!pi.IsOut"); Expression promoted = this.PromoteExpression(args[i], pi.ParameterType, false); if (promoted == null) { return false; } promotedArgs[i] = promoted; } method.Args = promotedArgs; return true; } ///Promotes the specified expression to the given type if necessary. /// Expression to promote. /// Type to change expression to. /// Whether an exact type is required; false implies a compatible type is OK. ///Expression with the promoted type. private Expression PromoteExpression(Expression expr, Type type, bool exact) { Debug.Assert(expr != null, "expr != null"); Debug.Assert(type != null, "type != null"); if (expr.Type == type) { return expr; } ConstantExpression ce = expr as ConstantExpression; if (ce != null) { if (ce == NullLiteral) { if (TypeAllowsNull(type)) { return Expression.Constant(null, type); } } else { string text; if (this.literals.TryGetValue(ce, out text)) { Type target = GetNonNullableType(type); object value = null; if (ce.Type == typeof(string) && target == typeof(Type)) { if (WebConvert.TryRemoveQuotes(ref text)) { ResourceType resourceType = this.provider.TryResolveTypeName(text); if (resourceType != null) { value = resourceType.Type; } } } else { switch (Type.GetTypeCode(ce.Type)) { case TypeCode.Int32: case TypeCode.Int64: value = ParseNumber(text, target); break; case TypeCode.Double: if (target == typeof(decimal)) { value = ParseNumber(text, target); } break; } } if (value != null) { return Expression.Constant(value, type); } } } } if (IsCompatibleWith(expr.Type, type)) { if (type.IsValueType || exact) { return Expression.Convert(expr, type); } return expr; } // Allow promotion from nullable to non-nullable by directly accessing underlying value. if (IsNullableType(expr.Type) && type.IsValueType) { Expression valueAccessExpression = Expression.Property(expr, "Value"); valueAccessExpression = this.PromoteExpression(valueAccessExpression, type, exact); return valueAccessExpression; } return null; } ///Checks that the current token has the specified identifier. /// Identifier to check. ///true if the current token is an identifier with the specified text. private bool TokenIdentifierIs(string id) { return this.CurrentToken.IdentifierIs(id); } ///Validates the current token is of the specified kind. /// Expected token kind. private void ValidateToken(TokenId t) { if (this.CurrentToken.Id != t) { throw ParseError(Strings.RequestQueryParser_SyntaxError(this.CurrentToken.Position)); } } #region Recursion control. ///Marks the fact that a recursive method was entered, and checks that the depth is allowed. private void RecurseEnter() { this.recursionDepth++; Debug.Assert(this.recursionDepth <= RecursionLimit, "this.recursionDepth <= recursionLimit"); if (this.recursionDepth == RecursionLimit) { throw DataServiceException.CreateDeepRecursion(RecursionLimit); } } ///Marks the fact that a recursive method is leaving.. private void RecurseLeave() { this.recursionDepth--; Debug.Assert(0 <= this.recursionDepth, "0 <= this.recursionDepth"); Debug.Assert(this.recursionDepth < RecursionLimit, "this.recursionDepth < recursionLimit"); } #endregion Recursion control. ///Use this class to encapsulate method information. [DebuggerDisplay("MethodData {methodBase}")] private class MethodData { #region Private fields. ///Described method. private readonly MethodBase methodBase; ///Parameters for method. private readonly ParameterInfo[] parameters; ///Argument expressions. private Expression[] args; #endregion Private fields. #region Constructors. ///Initializes a new /// Described method /// Parameters for method. public MethodData(MethodBase method, ParameterInfo[] parameters) { this.methodBase = method; this.parameters = parameters; } #endregion Constructors. #region Properties. ///instance. Argument expressions. public Expression[] Args { get { return this.args; } set { this.args = value; } } ///Described method. public MethodBase MethodBase { get { return this.methodBase; } } ///Parameters for method. public ParameterInfo[] Parameters { get { return this.parameters; } } ///Enumeration of parameter types. public IEnumerableParameterTypes { get { foreach (ParameterInfo parameter in this.Parameters) { yield return parameter.ParameterType; } } } #endregion Properties. } } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. //---------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // //// Provides a type to parse expressions in request queries. // // // @owner [....] //--------------------------------------------------------------------- namespace System.Data.Services.Parsing { #region Namespaces. using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Data.Services.Parsing; using System.Data.Services.Providers; #if ASTORIA_OPEN_OBJECT using System.Data.Services.OpenTypes; #endif #endregion Namespaces. ////// This class provides static methods to parse query options and compose /// them on an existing query. /// internal static class RequestQueryParser { #region Fields. ///Constant for "null" literal. internal static readonly ConstantExpression NullLiteral = Expression.Constant(null); #endregion Fields. #region Internal methods. ///Gets a type for /// Type to base resulting type on. ///that allows null values. /// internal static Type GetTypeAllowingNull(Type type) { Debug.Assert(type != null, "type != null"); return TypeAllowsNull(type) ? type : typeof(Nullable<>).MakeGenericType(type); } ///if it's a reference or Nullable<> type; /// Nullable< > otherwise. /// Checks whether the specified type is a generic nullable type. /// Type to check. ///true if internal static bool IsNullableType(Type type) { return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); } ///is nullable; false otherwise. Checks whether /// Expression to check. ///is a null constant. true if internal static bool IsNullConstant(Expression expression) { Debug.Assert(expression != null, "expression != null"); return expression == NullLiteral || (expression.NodeType == ExpressionType.Constant && ((ConstantExpression)expression).Value == null); } ///is a null constant; false otherwise. Checks whether the specified /// Type to check. ///can be assigned null. true if type is a reference type or a Nullable type; false otherwise. internal static bool TypeAllowsNull(Type type) { Debug.Assert(type != null, "type != null"); return !type.IsValueType || IsNullableType(type); } ///Sorts a query like a SQL ORDER BY clause does. /// Service with data and configuration. /// Original source for query. /// Ordering definition to compose. ///The composed query. internal static IQueryable OrderBy(IDataService service, IQueryable source, string ordering) { Debug.Assert(service != null, "service != null"); Debug.Assert(source != null, "source != null"); Debug.Assert(ordering != null, "ordering != null"); ParameterExpression parameterForIt = Expression.Parameter(source.ElementType, "it"); ExpressionParser parser = new ExpressionParser(service, parameterForIt, ordering); IEnumerableorderings = parser.ParseOrdering(); Expression queryExpr = source.Expression; string methodAsc = "OrderBy"; string methodDesc = "OrderByDescending"; foreach (DynamicOrdering o in orderings) { Type selectorType = o.Selector.Type; if (!service.Provider.GetTypeIsOrdered(selectorType)) { string resourceTypeName = WebUtil.GetTypeName(service.Provider, o.Selector.Type); throw DataServiceException.CreateBadRequestError(Strings.RequestQueryParser_OrderByDoesNotSupportType(resourceTypeName)); } queryExpr = Expression.Call( typeof(Queryable), o.Ascending ? methodAsc : methodDesc, new Type[] { source.ElementType, selectorType }, queryExpr, Expression.Quote(Expression.Lambda(o.Selector, parameterForIt))); methodAsc = "ThenBy"; methodDesc = "ThenByDescending"; } return source.Provider.CreateQuery(queryExpr); } /// Filters a query like a SQL WHERE clause does. /// Service with data and configuration. /// Original source for query. /// Predicate to compose. ///The composed query. internal static IQueryable Where(IDataService service, IQueryable source, string predicate) { Debug.Assert(service != null, "service != null"); Debug.Assert(service != null, "source != null"); Debug.Assert(predicate != null, "predicate != null"); LambdaExpression lambda = ParseLambdaForWhere(service, source.ElementType, predicate); ////Trace.WriteLine("predicate=" + predicate + "; lambda=" + lambda.ToString()); return source.Provider.CreateQuery( Expression.Call(typeof(Queryable), "Where", new Type[] { source.ElementType }, source.Expression, Expression.Quote(lambda))); } #endregion Internal methods. #region Private methods. ///Parses a lambda expression. /// Service with data and configuration. /// Type for "it" contextual variable. /// Expression to parse. ///The parsed expression. private static LambdaExpression ParseLambdaForWhere(IDataService service, Type typeForIt, string expression) { Debug.Assert(service != null, "service != null"); Debug.Assert(typeForIt != null, "typeForIt != null"); Debug.Assert(expression != null, "expression != null"); ParameterExpression parameterForIt = Expression.Parameter(typeForIt, "it"); ExpressionParser parser = new ExpressionParser(service, parameterForIt, expression); return Expression.Lambda(parser.ParseWhere(), parameterForIt); } #endregion Private methods. ///Use this class to define ordering for resources. private class DynamicOrdering { #region Private fields. ///Whether the order is ascending. private readonly bool ascending; ///Selector expression in ordering. private readonly Expression selector; #endregion Private fields. ///Initializes a new /// Selector expression in ordering. /// Whether the order is ascending. internal DynamicOrdering(Expression selector, bool ascending) { this.selector = selector; this.ascending = ascending; } #region Properties. ///instance. Whether the order is ascending. internal bool Ascending { get { return this.ascending; } } ///Selector expression in ordering. internal Expression Selector { get { return this.selector; } } #endregion Properties. } ///Use this class to parse an expression in the Astoria URI format. [DebuggerDisplay("ExpressionParser ({lexer.text})")] private class ExpressionParser { #region Fields. ///Maximum recursion limit on deserializer. private const int RecursionLimit = 100; ///A type that is not numeric. private const int NumericTypeNotNumeric = 0; ///A type that is a char, single, double or decimal. private const int NumericTypeNotIntegral = 1; ///A type that is a signed integral. private const int NumericTypeSignedIntegral = 2; ///A type that is an unsigned integral. private const int NumericTypeUnsignedIntegral = 3; ///Empty Expressions array. private static readonly Expression[] emptyExpressions = new Expression[0]; ///Constant for "true" literal. private static readonly ConstantExpression trueLiteral = Expression.Constant(true); ///Constant for "false" literal. private static readonly ConstantExpression falseLiteral = Expression.Constant(false); ///Dictionary of system functions. private static readonly Dictionaryfunctions = FunctionDescription.CreateFunctions(); /// Provider of data and metadata. private readonly IDataServiceProvider provider; ///Service with data and configuration. private readonly IDataService service; ///Literals. private Dictionaryliterals; /// "it" contextual parameter. private ParameterExpression it; ///Expression lexer. private ExpressionLexer lexer; ///Whether the expression tree should propagate nulls explicitly. private bool nullPropagationRequired; ///Depth of recursion. private int recursionDepth; #endregion Fields. #region Constructors. ///Initializes a new /// Service with data and configuration. /// Parameters for the current "it" context. /// Expression to parse. internal ExpressionParser(IDataService service, ParameterExpression parameterForIt, string expression) { Debug.Assert(service != null, "service != null"); Debug.Assert(expression != null, "expression != null"); Debug.Assert(parameterForIt != null, "parameterForIt != null"); this.service = service; this.provider = service.Provider; this.nullPropagationRequired = this.provider.NullPropagationRequired; this.literals = new Dictionary. (); this.it = parameterForIt; this.lexer = new ExpressionLexer(expression); } #endregion Constructors. /// Current token being processed. private Token CurrentToken { get { return this.lexer.CurrentToken; } set { this.lexer.CurrentToken = value; } } ///Parses the text expression for a .Where method invocation. ///The parsed expression. internal Expression ParseWhere() { int exprPos = this.lexer.Position; Expression expr = this.ParseExpression(); #if ASTORIA_OPEN_OBJECT if (IsOpenPropertyExpression(expr)) { expr = Expression.Convert(expr, typeof(bool)); } else #endif if (IsNullConstant(expr)) { expr = falseLiteral; } else if (expr.Type == typeof(bool?)) { Expression test = Expression.Equal(expr, Expression.Constant(null, typeof(bool?))); expr = Expression.Condition(test, falseLiteral, Expression.Property(expr, "Value")); } else if (expr.Type != typeof(bool)) { throw ParseError(Strings.RequestQueryParser_ExpressionTypeMismatch(this.GetTypeName(typeof(bool)), exprPos)); } this.lexer.ValidateToken(TokenId.End); Debug.Assert(expr != null, "expr != null"); Debug.Assert(expr.Type == typeof(bool), "expr.Type(" + expr.Type + ") == typeof(bool)"); return expr; } ///Parses the text expression for ordering. ///An enumeration of orderings. internal IEnumerableParseOrdering() { List orderings = new List (); while (true) { Expression expr = this.ParseExpression(); bool ascending = true; if (this.TokenIdentifierIs(ExpressionConstants.KeywordAscending)) { this.lexer.NextToken(); } else if (this.TokenIdentifierIs(ExpressionConstants.KeywordDescending)) { this.lexer.NextToken(); ascending = false; } orderings.Add(new DynamicOrdering(expr, ascending)); if (this.CurrentToken.Id != TokenId.Comma) { break; } this.lexer.NextToken(); } this.ValidateToken(TokenId.End); return orderings; } /// Compares two byte arrays for equality. /// First byte array.Second byte array. ///true if the arrays are equal; false otherwise. private static bool ByteArraysEqual(byte[] b0, byte[] b1) { if (b0 == b1) { return true; } if (b0 == null || b1 == null) { return false; } if (b0.Length != b1.Length) { return false; } for (int i = 0; i < b0.Length; i++) { if (b0[i] != b1[i]) { return false; } } return true; } ///Compares two byte arrays for equality. /// First byte array.Second byte array. ///true if the arrays are not equal; false otherwise. private static bool ByteArraysNotEqual(byte[] b0, byte[] b1) { return !ByteArraysEqual(b0, b1); } ///Gets a non-nullable version of the specified type. /// Type to get non-nullable version for. ////// private static Type GetNonNullableType(Type type) { return Nullable.GetUnderlyingType(type) ?? type; } ///if type is a reference type or a /// non-nullable type; otherwise, the underlying value type. /// Checks whether the specified type is a signed integral type. /// Type to check. ///true if private static bool IsSignedIntegralType(Type type) { return GetNumericTypeKind(type) == NumericTypeSignedIntegral; } ///is a signed integral type; false otherwise. Checks whether the specified type is an unsigned integral type. /// Type to check. ///true if private static bool IsUnsignedIntegralType(Type type) { return GetNumericTypeKind(type) == NumericTypeUnsignedIntegral; } ///is an unsigned integral type; false otherwise. Gets a flag for the numeric kind of type. /// Type to get numeric kind for. ////// One of NumericTypeNotNumeric; NumericTypeNotIntegral if it's char, /// single, double or decimal; NumericTypeSignedIntegral, or NumericTypeUnsignedIntegral. /// private static int GetNumericTypeKind(Type type) { type = GetNonNullableType(type); Debug.Assert(!type.IsEnum, "!type.IsEnum"); switch (Type.GetTypeCode(type)) { case TypeCode.Char: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return NumericTypeNotIntegral; case TypeCode.SByte: case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: return NumericTypeSignedIntegral; case TypeCode.Byte: return NumericTypeUnsignedIntegral; default: return NumericTypeNotNumeric; } } ///Checks whether type is a (possibly nullable) enumeration type. /// Type to check. ///true if type is an enumeration or a nullable enumeration; false otherwise. private static bool IsEnumType(Type type) { return GetNonNullableType(type).IsEnum; } ///Returns an object that can enumerate the specified type and its supertypes. /// Type to based enumeration on. ///An object that can enumerate the specified type and its supertypes. private static IEnumerableSelfAndBaseTypes(Type type) { if (type.IsInterface) { List types = new List (); AddInterface(types, type); return types; } return SelfAndBaseClasses(type); } /// Returns an object that can enumerate the specified type and its supertypes. /// Type to based enumeration on. ///An object that can enumerate the specified type and its supertypes. private static IEnumerableSelfAndBaseClasses(Type type) { while (type != null) { yield return type; type = type.BaseType; } } /// Adds an interface type to a list of types, including inherited interfaces. /// Types list ot add to. /// Interface type to add. private static void AddInterface(Listtypes, Type type) { if (!types.Contains(type)) { types.Add(type); foreach (Type t in type.GetInterfaces()) { AddInterface(types, t); } } } /// Finds the best applicable methods from the specified array that match the arguments. /// Candidate methods. /// Argument expressions. ///Best applicable methods. private static MethodData[] FindBestApplicableMethods(MethodData[] applicable, Expression[] args) { Debug.Assert(applicable != null, "applicable != null"); Listresult = new List (); foreach (MethodData method in applicable) { bool betterThanAllOthers = true; foreach (MethodData otherMethod in applicable) { if (otherMethod != method && IsBetterThan(args, otherMethod, method)) { betterThanAllOthers = false; break; } } if (betterThanAllOthers) { result.Add(method); } } return result.ToArray(); } /// Parses the specified text into a number. /// Text to parse. /// Type to parse into. ///The parsed number. private static object ParseNumber(string text, Type type) { TypeCode tc = Type.GetTypeCode(GetNonNullableType(type)); switch (tc) { case TypeCode.SByte: sbyte sb; if (sbyte.TryParse(text, out sb)) { return sb; } break; case TypeCode.Byte: byte b; if (byte.TryParse(text, out b)) { return b; } break; case TypeCode.Int16: short s; if (short.TryParse(text, out s)) { return s; } break; case TypeCode.Int32: int i; if (int.TryParse(text, out i)) { return i; } break; case TypeCode.Int64: long l; if (long.TryParse(text, out l)) { return l; } break; case TypeCode.Single: float f; if (float.TryParse(text, out f)) { return f; } break; case TypeCode.Double: double d; if (double.TryParse(text, out d)) { return d; } break; case TypeCode.Decimal: decimal e; if (decimal.TryParse(text, out e)) { return e; } break; } return null; } ///Checks whether the source type is compatible with the value type. /// Source type. /// Target type. ///true if source can be used in place of target; false otherwise. private static bool IsCompatibleWith(Type source, Type target) { if (source == target) { return true; } if (!target.IsValueType) { return target.IsAssignableFrom(source); } Type sourceType = GetNonNullableType(source); Type targetType = GetNonNullableType(target); //// This rule stops the parser from considering nullable types as incompatible //// with non-nullable types. We have however implemented this rule because we just //// access underlying rules. C# requires an explicit .Value access, and EDM has no //// nullablity on types and (at the model level) implements null propagation. //// //// if (sourceType != source && targetType == target) //// { //// return false; //// } TypeCode sourceCode = sourceType.IsEnum ? TypeCode.Object : Type.GetTypeCode(sourceType); TypeCode targetCode = targetType.IsEnum ? TypeCode.Object : Type.GetTypeCode(targetType); switch (sourceCode) { case TypeCode.SByte: switch (targetCode) { case TypeCode.SByte: case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return true; } break; case TypeCode.Byte: switch (targetCode) { case TypeCode.Byte: case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return true; } break; case TypeCode.Int16: switch (targetCode) { case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return true; } break; case TypeCode.Int32: switch (targetCode) { case TypeCode.Int32: case TypeCode.Int64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return true; } break; case TypeCode.Int64: switch (targetCode) { case TypeCode.Int64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return true; } break; case TypeCode.Single: switch (targetCode) { case TypeCode.Single: case TypeCode.Double: return true; } break; default: if (sourceType == targetType) { return true; } break; } // Anything can be converted to something that's *exactly* an object. #if ASTORIA_OPEN_OBJECT if (target == typeof(object)) { return true; } #endif return false; } ////// Checks whether one type list is a better fit than other for the /// specified expressions. /// /// Expressions for arguments. /// First type list to check. /// Second type list to check. ////// true if private static bool IsBetterThan(Expression[] args, IEnumerablehas better parameter matching than . /// firstCandidate, IEnumerable secondCandidate) { bool better = false; using (IEnumerator first = firstCandidate.GetEnumerator()) using (IEnumerator second = secondCandidate.GetEnumerator()) { for (int i = 0; i < args.Length; i++) { first.MoveNext(); second.MoveNext(); int c = CompareConversions(args[i].Type, first.Current, second.Current); if (c < 0) { return false; } else if (c > 0) { better = true; } } } return better; } /// /// Checks whether one method is a better fit than other for the /// specified expressions. /// /// Expressions for arguments. /// First method to check. /// Second method to check. ////// true if private static bool IsBetterThan(Expression[] args, MethodData m1, MethodData m2) { Debug.Assert(args != null, "args != null"); Debug.Assert(m1 != null, "m1 != null"); Debug.Assert(m2 != null, "m2 != null"); return IsBetterThan(args, m1.ParameterTypes, m2.ParameterTypes); } ///has better parameter matching than . /// Checks which conversion is better. /// Source type. /// First candidate type to convert to. /// Second candidate type to convert to. ////// Return 1 if s -> t1 is a better conversion than s -> t2 /// Return -1 if s -> t2 is a better conversion than s -> t1 /// Return 0 if neither conversion is better /// private static int CompareConversions(Type source, Type targetA, Type targetB) { // If both types are exactly the same, there is no preference. if (targetA == targetB) { return 0; } // Look for exact matches. if (source == targetA) { return 1; } else if (source == targetB) { return -1; } // If one is compatible and the other is not, choose the compatible type. bool compatibleT1AndT2 = IsCompatibleWith(targetA, targetB); bool compatibleT2AndT1 = IsCompatibleWith(targetB, targetA); if (compatibleT1AndT2 && !compatibleT2AndT1) { return 1; } else if (compatibleT2AndT1 && !compatibleT1AndT2) { return -1; } // Prefer to keep the original nullability. bool sourceNullable = IsNullableType(source); bool typeNullableA = IsNullableType(targetA); bool typeNullableB = IsNullableType(targetB); if (sourceNullable == typeNullableA && sourceNullable != typeNullableB) { return 1; } else if (sourceNullable != typeNullableA && sourceNullable == typeNullableB) { return -1; } // Prefer signed to unsigned. if (IsSignedIntegralType(targetA) && IsUnsignedIntegralType(targetB)) { return 1; } else if (IsSignedIntegralType(targetB) && IsUnsignedIntegralType(targetA)) { return -1; } // Prefer non-object to object. if (targetA != typeof(object) && targetB == typeof(object)) { return 1; } else if (targetB != typeof(object) && targetA == typeof(object)) { return -1; } return 0; } ///Generates an Equal expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateEqual(Expression left, Expression right) { #if ASTORIA_OPEN_OBJECT if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return LateBoundMethods.EqualExpression(left, right); } #endif if (left.Type == typeof(byte[])) { MethodInfo byteArrayCompareMethod = typeof(ExpressionParser).GetMethod("ByteArraysEqual", BindingFlags.NonPublic | BindingFlags.Static); return Expression.Equal(left, right, false, byteArrayCompareMethod); } return Expression.Equal(left, right); } ///Generates a NotEqual expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateNotEqual(Expression left, Expression right) { #if ASTORIA_OPEN_OBJECT if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return LateBoundMethods.NotEqualExpression(left, right); } #endif if (left.Type == typeof(byte[])) { MethodInfo byteArrayCompareMethod = typeof(ExpressionParser).GetMethod("ByteArraysNotEqual", BindingFlags.NonPublic | BindingFlags.Static); return Expression.NotEqual(left, right, false, byteArrayCompareMethod); } return Expression.NotEqual(left, right); } ///Generates a GreaterThan comparison expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateGreaterThan(Expression left, Expression right) { #if ASTORIA_OPEN_OBJECT if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return LateBoundMethods.GreaterThanExpression(left, right); } #endif if (left.Type == typeof(string)) { return Expression.GreaterThan(left, right, false, GetStringCompareMethod("StringGreaterThanMethod")); } return Expression.GreaterThan(left, right); } ///Generates a GreaterThanOrEqual comparsion expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateGreaterThanEqual(Expression left, Expression right) { #if ASTORIA_OPEN_OBJECT if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return LateBoundMethods.GreaterThanOrEqualExpression(left, right); } #endif if (left.Type == typeof(string)) { return Expression.GreaterThanOrEqual(left, right, false, GetStringCompareMethod("StringGreaterThanOrEqualMethod")); } return Expression.GreaterThanOrEqual(left, right); } ///Generates a LessThan comparsion expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateLessThan(Expression left, Expression right) { #if ASTORIA_OPEN_OBJECT if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return LateBoundMethods.LessThanExpression(left, right); } #endif if (left.Type == typeof(string)) { return Expression.LessThan(left, right, false, GetStringCompareMethod("StringLessThanMethod")); } return Expression.LessThan(left, right); } ///Generates a LessThanOrEqual comparsion expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateLessThanEqual(Expression left, Expression right) { #if ASTORIA_OPEN_OBJECT if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return LateBoundMethods.LessThanOrEqualExpression(left, right); } #endif if (left.Type == typeof(string)) { return Expression.LessThanOrEqual(left, right, false, GetStringCompareMethod("StringLessThanOrEqualMethod")); } return Expression.LessThanOrEqual(left, right); } ///Generates an addition expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateAdd(Expression left, Expression right) { #if ASTORIA_OPEN_OBJECT if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return LateBoundMethods.AddExpression(left, right); } #endif return Expression.Add(left, right); } ///Generates a subtract expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateSubtract(Expression left, Expression right) { #if ASTORIA_OPEN_OBJECT if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return LateBoundMethods.SubtractExpression(left, right); } #endif return Expression.Subtract(left, right); } ///Gets a static method to compare strings. /// Name of method. ///The private static MethodInfo GetStringCompareMethod(string methodName) { Debug.Assert(methodName != null, "methodName != null"); MethodInfo result = typeof(ExpressionParser).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static); Debug.Assert(result != null, "result != null"); return result; } #if ASTORIA_OPEN_OBJECT ///for the method. /// Checks whether the specified /// Non-nullis part of an open property expression. /// to check. /// true if private static bool IsOpenPropertyExpression(Expression expression) { Debug.Assert(expression != null, "expression != null"); return expression != NullLiteral && expression.Type == typeof(object); } #endif ///is based on an open property; false otherwise. Checks whether the left string is less than or equal to the right string. /// Left value.Right value. ///true if the left string is less than or equal to the right string. private static bool StringLessThanOrEqualMethod(string left, string right) { return string.CompareOrdinal(left, right) <= 0; } ///Checks whether the left string is less than the right string. /// Left value.Right value. ///true if the left string is less than the right string. private static bool StringLessThanMethod(string left, string right) { return string.CompareOrdinal(left, right) < 0; } ///Checks whether the left string is greater than or equal to the right string. /// Left value.Right value. ///true if the left string is greater than or equal to the right string. private static bool StringGreaterThanOrEqualMethod(string left, string right) { return string.CompareOrdinal(left, right) >= 0; } ///Checks whether the left string is greater than the right string. /// Left value.Right value. ///true if the left string is greater than the right string. private static bool StringGreaterThanMethod(string left, string right) { return string.CompareOrdinal(left, right) > 0; } ///Gets a static method by name. /// Name of method to get. /// Left expression to resolve method from and to use as argument. /// Right expression. ///The method. private static MethodInfo GetStaticMethod(string methodName, Expression left, Expression right) { return left.Type.GetMethod(methodName, new Type[] { left.Type, right.Type }); } ///Generates a static method call. /// Method name. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right) { return Expression.Call(null, GetStaticMethod(methodName, left, right), new Expression[] { left, right }); } ///Creates an exception for a parse error. /// Message text. ///A new Exception. private static Exception ParseError(string message) { return DataServiceException.CreateSyntaxError(message); } ///Checks that the given token has the specified identifier. /// Token to check /// Identifier to check. ///true if private static bool TokenIdentifierIs(Token token, string id) { return token.Id == TokenId.Identifier && String.Equals(id, token.Text, StringComparison.OrdinalIgnoreCase); } #region Parsing. ///is an identifier with the specified text. Handles a ?: operator - not supported. ///The parsed expression. private Expression ParseExpression() { this.RecurseEnter(); Expression expr = this.ParseLogicalOr(); this.RecurseLeave(); return expr; } ///Handles or operator. ///The parsed expression. private Expression ParseLogicalOr() { this.RecurseEnter(); Expression left = this.ParseLogicalAnd(); while (this.TokenIdentifierIs(ExpressionConstants.KeywordOr)) { Token op = this.CurrentToken; this.lexer.NextToken(); Expression right = this.ParseLogicalAnd(); this.CheckAndPromoteOperands(typeof(OperationSignatures.ILogicalSignatures), op.Text, ref left, ref right, op.Position); #if ASTORIA_OPEN_OBJECT if (left.Type == typeof(object) || right.Type == typeof(object)) { left = LateBoundMethods.OrElseExpression(left, right); if (left == null) { throw ParseError(Strings.RequestQueryParser_BooleanExpressionsExpectedFor(op.Text)); } } else #endif { left = Expression.OrElse(left, right); } } this.RecurseLeave(); return left; } ///Handles and operator. ///The parsed expression. private Expression ParseLogicalAnd() { this.RecurseEnter(); Expression left = this.ParseComparison(); while (this.TokenIdentifierIs(ExpressionConstants.KeywordAnd)) { Token op = this.CurrentToken; this.lexer.NextToken(); Expression right = this.ParseComparison(); this.CheckAndPromoteOperands(typeof(OperationSignatures.ILogicalSignatures), op.Text, ref left, ref right, op.Position); #if ASTORIA_OPEN_OBJECT if (left.Type == typeof(object) || right.Type == typeof(object)) { left = LateBoundMethods.AndAlsoExpression(left, right); if (left == null) { throw ParseError(Strings.RequestQueryParser_BooleanExpressionsExpectedFor(op.Text)); } } else #endif { left = Expression.AndAlso(left, right); } } this.RecurseLeave(); return left; } ///Handles eq, ne, lt, gt, le, ge operators. ///The parsed expression. private Expression ParseComparison() { this.RecurseEnter(); Expression left = this.ParseAdditive(); while (this.CurrentToken.IsComparisonOperator) { Token op = this.CurrentToken; this.lexer.NextToken(); Expression right = this.ParseAdditive(); bool equality = op.IsEqualityOperator; if (equality && !left.Type.IsValueType && !right.Type.IsValueType) { if (left.Type != right.Type) { if (IsNullConstant(left)) { left = Expression.Constant(null, right.Type); } else if (IsNullConstant(right)) { right = Expression.Constant(null, left.Type); } else if (left.Type.IsAssignableFrom(right.Type)) { right = Expression.Convert(right, left.Type); } else if (right.Type.IsAssignableFrom(left.Type)) { left = Expression.Convert(left, right.Type); } else { throw this.IncompatibleOperandsError(op.Text, left, right, op.Position); } } } else if (left == NullLiteral || right == NullLiteral) { if (!equality) { throw ParseError( Strings.RequestQueryParser_NullOperatorUnsupported(op.Text, op.Position, this.lexer.ExpressionText)); } // Because we don't have an explicit "is null" check, literal comparisons // to null are special. if (!TypeAllowsNull(left.Type)) { left = Expression.Convert(left, typeof(Nullable<>).MakeGenericType(left.Type)); } else if (!TypeAllowsNull(right.Type)) { right = Expression.Convert(right, typeof(Nullable<>).MakeGenericType(right.Type)); } } else { // Enums should be checked here for promotion when supported, but they aren't in this version. Debug.Assert(!IsEnumType(left.Type), "!IsEnumType(left.Type)"); Debug.Assert(!IsEnumType(right.Type), "!IsEnumType(right.Type)"); Type signatures = equality ? typeof(OperationSignatures.IEqualitySignatures) : typeof(OperationSignatures.IRelationalSignatures); this.CheckAndPromoteOperands(signatures, op.Text, ref left, ref right, op.Position); } Debug.Assert(op.Id == TokenId.Identifier, "op.id == TokenId.Identifier"); switch (op.Text) { case ExpressionConstants.KeywordEqual: left = GenerateEqual(left, right); break; case ExpressionConstants.KeywordNotEqual: left = GenerateNotEqual(left, right); break; case ExpressionConstants.KeywordGreaterThan: left = GenerateGreaterThan(left, right); break; case ExpressionConstants.KeywordGreaterThanOrEqual: left = GenerateGreaterThanEqual(left, right); break; case ExpressionConstants.KeywordLessThan: left = GenerateLessThan(left, right); break; case ExpressionConstants.KeywordLessThanOrEqual: left = GenerateLessThanEqual(left, right); break; } } this.RecurseLeave(); return left; } ///Handles +, -, & operators (& for string concat, not supported). ///The parsed expression. private Expression ParseAdditive() { this.RecurseEnter(); Expression left = this.ParseMultiplicative(); while (this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordAdd) || this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordSub)) { Token op = this.CurrentToken; this.lexer.NextToken(); Expression right = this.ParseMultiplicative(); if (op.IdentifierIs(ExpressionConstants.KeywordAdd)) { this.CheckAndPromoteOperands(typeof(OperationSignatures.IAddSignatures), op.Text, ref left, ref right, op.Position); left = GenerateAdd(left, right); } else { Debug.Assert(ExpressionParser.TokenIdentifierIs(op, ExpressionConstants.KeywordSub), "ExpressionParser.TokenIdentifierIs(op, ExpressionConstants.KeywordSub)"); this.CheckAndPromoteOperands(typeof(OperationSignatures.ISubtractSignatures), op.Text, ref left, ref right, op.Position); left = GenerateSubtract(left, right); } } this.RecurseLeave(); return left; } ///Handles mul, div, mod operators. ///The parsed expression. private Expression ParseMultiplicative() { this.RecurseEnter(); Expression left = this.ParseUnary(); while (this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordMultiply) || this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordDivide) || this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordModulo)) { Token op = this.CurrentToken; this.lexer.NextToken(); Expression right = this.ParseUnary(); this.CheckAndPromoteOperands(typeof(OperationSignatures.IArithmeticSignatures), op.Text, ref left, ref right, op.Position); if (op.IdentifierIs(ExpressionConstants.KeywordMultiply)) { #if ASTORIA_OPEN_OBJECT if (left.Type == typeof(object) || right.Type == typeof(object)) { left = LateBoundMethods.MultiplyExpression(left, right); } else #endif { left = Expression.Multiply(left, right); } } else if (op.IdentifierIs(ExpressionConstants.KeywordDivide)) { #if ASTORIA_OPEN_OBJECT if (left.Type == typeof(object) || right.Type == typeof(object)) { left = LateBoundMethods.DivideExpression(left, right); } else #endif { left = Expression.Divide(left, right); } } else { Debug.Assert(op.IdentifierIs(ExpressionConstants.KeywordModulo), "op.IdentifierIs(ExpressionConstants.KeywordModulo)"); #if ASTORIA_OPEN_OBJECT if (left.Type == typeof(object) || right.Type == typeof(object)) { left = LateBoundMethods.ModuloExpression(left, right); } else #endif { left = Expression.Modulo(left, right); } } } this.RecurseLeave(); return left; } ///Handles -, not unary operators. ///The parsed expression. private Expression ParseUnary() { this.RecurseEnter(); if (this.CurrentToken.Id == TokenId.Minus || this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordNot)) { Token op = this.CurrentToken; this.lexer.NextToken(); if (op.Id == TokenId.Minus && (ExpressionLexer.IsNumeric(this.CurrentToken.Id))) { Token numberLiteral = this.CurrentToken; numberLiteral.Text = "-" + numberLiteral.Text; numberLiteral.Position = op.Position; this.CurrentToken = numberLiteral; this.RecurseLeave(); return this.ParsePrimary(); } Expression expr = this.ParseUnary(); if (op.Id == TokenId.Minus) { this.CheckAndPromoteOperand(typeof(OperationSignatures.INegationSignatures), op.Text, ref expr, op.Position); #if ASTORIA_OPEN_OBJECT if (expr.Type == typeof(object)) { expr = LateBoundMethods.NegateExpression(expr); } else #endif { expr = Expression.Negate(expr); } } else { this.CheckAndPromoteOperand(typeof(OperationSignatures.INotSignatures), op.Text, ref expr, op.Position); #if ASTORIA_OPEN_OBJECT if (expr.Type == typeof(object)) { expr = LateBoundMethods.NotExpression(expr); } else #endif if (expr.Type == typeof(bool) || expr.Type == typeof(Nullable)) { // Expression.Not will take numerics and apply '~' to them, thus the extra check here. expr = Expression.Not(expr); } else { throw ParseError(Strings.RequestQueryParser_NotDoesNotSupportType(expr.Type)); } } this.RecurseLeave(); return expr; } this.RecurseLeave(); return this.ParsePrimary(); } /// Handles primary expressions. ///The parsed expression. private Expression ParsePrimary() { this.RecurseEnter(); Expression expr = this.ParsePrimaryStart(); while (true) { if (this.CurrentToken.Id == TokenId.Slash) { this.lexer.NextToken(); expr = this.ParseMemberAccess(expr); } else { break; } } this.RecurseLeave(); return expr; } ///Handles the start of primary expressions. ///The parsed expression. private Expression ParsePrimaryStart() { switch (this.CurrentToken.Id) { case TokenId.BooleanLiteral: return this.ParseTypedLiteral(typeof(bool), XmlConstants.EdmBooleanTypeName); case TokenId.DateTimeLiteral: return this.ParseTypedLiteral(typeof(DateTime), XmlConstants.EdmDateTimeTypeName); case TokenId.DecimalLiteral: return this.ParseTypedLiteral(typeof(decimal), XmlConstants.EdmDecimalTypeName); case TokenId.NullLiteral: return this.ParseNullLiteral(); case TokenId.Identifier: return this.ParseIdentifier(); case TokenId.StringLiteral: return this.ParseTypedLiteral(typeof(string), XmlConstants.EdmStringTypeName); case TokenId.Int64Literal: return this.ParseTypedLiteral(typeof(Int64), XmlConstants.EdmInt64TypeName); case TokenId.IntegerLiteral: return this.ParseTypedLiteral(typeof(Int32), XmlConstants.EdmInt32TypeName); case TokenId.DoubleLiteral: return this.ParseTypedLiteral(typeof(double), XmlConstants.EdmDoubleTypeName); case TokenId.SingleLiteral: return this.ParseTypedLiteral(typeof(Single), XmlConstants.EdmSingleTypeName); case TokenId.GuidLiteral: return this.ParseTypedLiteral(typeof(Guid), XmlConstants.EdmGuidTypeName); case TokenId.BinaryLiteral: return this.ParseTypedLiteral(typeof(byte[]), XmlConstants.EdmBinaryTypeName); case TokenId.OpenParen: return this.ParseParenExpression(); default: throw ParseError(Strings.RequestQueryParser_ExpressionExpected(this.CurrentToken.Position)); } } ///Handles parenthesized expressions. ///The parsed expression. private Expression ParseParenExpression() { if (this.CurrentToken.Id != TokenId.OpenParen) { throw ParseError(Strings.RequestQueryParser_OpenParenExpected(this.CurrentToken.Position)); } this.lexer.NextToken(); Expression e = this.ParseExpression(); if (this.CurrentToken.Id != TokenId.CloseParen) { throw ParseError(Strings.RequestQueryParser_CloseParenOrOperatorExpected(this.CurrentToken.Position)); } this.lexer.NextToken(); return e; } ///Handles identifiers. ///The parsed expression. private Expression ParseIdentifier() { this.ValidateToken(TokenId.Identifier); bool identifierIsFunction = this.lexer.PeekNextToken().Id == TokenId.OpenParen; if (identifierIsFunction) { return this.ParseIdentifierAsFunction(); } else { return this.ParseMemberAccess(this.it); } } ///Handles identifiers which have been recognized as functions. ///The parsed expression. private Expression ParseIdentifierAsFunction() { FunctionDescription[] functionDescriptions; Token functionToken = this.CurrentToken; if (!functions.TryGetValue(functionToken.Text, out functionDescriptions)) { throw ParseError(Strings.RequestQueryParser_UnknownFunction(functionToken.Text, functionToken.Position)); } this.lexer.NextToken(); Expression[] originalArguments = this.ParseArgumentList(); Expression[] arguments = this.nullPropagationRequired ? originalArguments : (Expression[])originalArguments.Clone(); FunctionDescription function = this.FindBestFunction(functionDescriptions, ref arguments); if (function == null) { string message = Strings.RequestQueryParser_NoApplicableFunction( functionToken.Text, functionToken.Position, FunctionDescription.BuildSignatureList(functionToken.Text, functionDescriptions)); throw ParseError(message); } // Special case for null propagation - we never strip nullability from expressions. if (this.nullPropagationRequired && function.IsTypeCast) { Expression typeExpression = arguments[arguments.Length - 1]; Debug.Assert(typeExpression != null, "typeExpression != null -- otherwise function finding failed."); Debug.Assert(typeExpression.Type == typeof(Type), "typeExpression.Type == typeof(Type) -- last argument is always type."); Type castTargetType = (Type)((ConstantExpression)typeExpression).Value; if (!TypeAllowsNull(castTargetType)) { arguments[arguments.Length - 1] = Expression.Constant(typeof(Nullable<>).MakeGenericType(castTargetType)); } } Expression result = function.ConversionFunction(this.it, arguments); if (this.nullPropagationRequired && !function.IsTypeCheck && !function.IsTypeCast) { Debug.Assert( originalArguments != arguments, "originalArguments != arguments -- arguments should have been cloned"); Debug.Assert( originalArguments.Length == arguments.Length, "originalArguments.Length == arguments.Length -- arguments should not be added/removed"); for (int i = 0; i < originalArguments.Length; i++) { result = this.ConsiderNullPropagation(originalArguments[i], result); } } return result; } ///Handles boolean literals. ///The parsed expression. private Expression ParseBooleanLiteral() { Debug.Assert(this.CurrentToken.Id == TokenId.BooleanLiteral, "this.CurrentToken.Id == TokenId.BooleanLiteral"); string originalText = this.CurrentToken.Text; this.lexer.NextToken(); if (originalText == ExpressionConstants.KeywordTrue) { return trueLiteral; } else { Debug.Assert(originalText == ExpressionConstants.KeywordFalse, "originalText == ExpressionConstants.KeywordFalse"); return falseLiteral; } } ///Handles typed literals. /// Expected type to be parsed. /// Expected type name. ///The constants expression produced by building the given literal. private Expression ParseTypedLiteral(Type targetType, string targetTypeName) { object targetValue; if (!WebConvert.TryKeyStringToPrimitive(this.CurrentToken.Text, targetType, out targetValue)) { string message = Strings.RequestQueryParser_UnrecognizedLiteral(targetTypeName, this.CurrentToken.Text, this.CurrentToken.Position); throw ParseError(message); } Expression result = this.CreateLiteral(targetValue, this.CurrentToken.Text); this.lexer.NextToken(); return result; } ///Handles 'null' literals. ///The parsed expression. private Expression ParseNullLiteral() { Debug.Assert(this.CurrentToken.Id == TokenId.NullLiteral, "this.CurrentToken.Id == TokenId.NullLiteral"); this.lexer.NextToken(); return NullLiteral; } ///Handles member access. /// Instance being accessed. ///The parsed expression. private Expression ParseMemberAccess(Expression instance) { Debug.Assert(instance != null, "instance != null"); Type type = instance.Type; int errorPos = this.lexer.Position; string id = this.CurrentToken.GetIdentifier(); this.lexer.NextToken(); // An open paren here would indicate calling a method in regular C# syntax. ResourceProperty property = this.FindProperty(type, id); if (property != null) { // We have a strongly-type property. Expression result = this.ConsiderNullPropagation(instance, Expression.Property(instance, property.PropertyInfo)); if (property.ResourceContainer != null) { Expression filter = DataServiceConfiguration.ComposeQueryInterceptors(this.service, property.ResourceContainer); if (filter != null) { // We did null propagation for accessing the property, but we may also need // to do null propagation on the property value itself (otherwise the interception // lambda needs to check the argument for null, which is probably unexpected). result = RequestQueryProcessor.ComposePropertyNavigation( result, (LambdaExpression)filter, this.provider.NullPropagationRequired); } } return result; } else { // Open types can have any members inside them #if ASTORIA_OPEN_OBJECT if (type == typeof(object) || OpenTypeAttribute.IsOpenType(type)) { // object LateBoundMethods.GetValue(object, string) Expression call = Expression.Call(null /* instance */, LateBoundMethods.GetValueMethodInfo, instance, Expression.Constant(id)); return this.ConsiderNullPropagation(instance, call); } else #endif { throw ParseError(Strings.RequestQueryParser_UnknownProperty(id, this.GetTypeName(type), errorPos)); } } } ///Handles argument lists. ///The parsed expressions. private Expression[] ParseArgumentList() { if (this.CurrentToken.Id != TokenId.OpenParen) { throw ParseError(Strings.RequestQueryParser_OpenParenExpected(this.CurrentToken.Position)); } this.lexer.NextToken(); Expression[] args = this.CurrentToken.Id != TokenId.CloseParen ? this.ParseArguments() : emptyExpressions; if (this.CurrentToken.Id != TokenId.CloseParen) { throw ParseError(Strings.RequestQueryParser_CloseParenOrCommaExpected(this.CurrentToken.Position)); } this.lexer.NextToken(); return args; } ///Handles comma-separated arguments. ///The parsed expressions. private Expression[] ParseArguments() { ListargList = new List (); while (true) { argList.Add(this.ParseExpression()); if (this.CurrentToken.Id != TokenId.Comma) { break; } this.lexer.NextToken(); } return argList.ToArray(); } #endregion Parsing. /// Creates a constant expression with the specified literal. /// Constant value. /// Value text. ///The created expression. private Expression CreateLiteral(object value, string text) { ConstantExpression expr = Expression.Constant(value); this.literals.Add(expr, text); return expr; } ///Finds the best fitting function for the specified arguments. /// Functions to consider. /// Arguments; if a best function is found, promoted arguments. ///The best fitting function; null if none found or ambiguous. private FunctionDescription FindBestFunction(FunctionDescription[] functions, ref Expression[] arguments) { Debug.Assert(functions != null, "functions != null"); ListapplicableFunctions = new List (functions.Length); List applicablePromotedArguments = new List (functions.Length); // Build a list of applicable functions (and cache their promoted arguments). foreach (FunctionDescription candidate in functions) { if (candidate.ParameterTypes.Length != arguments.Length) { continue; } Expression[] promotedArguments = new Expression[arguments.Length]; bool argumentsMatch = true; for (int i = 0; i < candidate.ParameterTypes.Length; i++) { promotedArguments[i] = this.PromoteExpression(arguments[i], candidate.ParameterTypes[i], true); if (promotedArguments[i] == null) { argumentsMatch = false; break; } } if (argumentsMatch) { applicableFunctions.Add(candidate); applicablePromotedArguments.Add(promotedArguments); } } // Return the best applicable function. if (applicableFunctions.Count == 0) { // No matching function. return null; } else if (applicableFunctions.Count == 1) { arguments = applicablePromotedArguments[0]; return applicableFunctions[0]; } else { // Find a single function which is better than all others. int bestFunctionIndex = -1; for (int i = 0; i < applicableFunctions.Count; i++) { bool betterThanAllOthers = true; for (int j = 0; j < applicableFunctions.Count; j++) { if (i != j && IsBetterThan(arguments, applicableFunctions[j].ParameterTypes, applicableFunctions[i].ParameterTypes)) { betterThanAllOthers = false; break; } } if (betterThanAllOthers) { if (bestFunctionIndex == -1) { bestFunctionIndex = i; } else { // Ambiguous. return null; } } } if (bestFunctionIndex == -1) { return null; } arguments = applicablePromotedArguments[bestFunctionIndex]; return applicableFunctions[bestFunctionIndex]; } } /// Rewrites an expression to propagate null values if necessary. /// Expression to check for null. /// Expression to yield ifdoes not yield null. /// The possibly rewriteen private Expression ConsiderNullPropagation(Expression element, Expression notNullExpression) { if (!this.nullPropagationRequired || element == this.it || !TypeAllowsNull(element.Type)) { return notNullExpression; } // Tiny optimization: remove the check on constants which are known not to be null. // Otherwise every string literal propagates out, which is correct but unnecessarily messy. if (element is ConstantExpression && element != NullLiteral) { return notNullExpression; } // ifTrue and ifFalse expressions must match exactly. We need to ensure that the 'false' // side is nullable, and the 'true' side is a null of the correct type. Expression test = Expression.Equal(element, Expression.Constant(null, element.Type)); Expression falseIf = notNullExpression; if (!TypeAllowsNull(falseIf.Type)) { falseIf = Expression.Convert(falseIf, typeof(Nullable<>).MakeGenericType(falseIf.Type)); } Expression trueIf = Expression.Constant(null, falseIf.Type); return Expression.Condition(test, trueIf, falseIf); } ///. Checks that the operand (possibly promoted) is valid for the specified operation. /// Type with signatures to match. /// Name of operation for error reporting. /// Expression for operand. /// Position for error reporting. private void CheckAndPromoteOperand(Type signatures, string operationName, ref Expression expr, int errorPos) { Debug.Assert(signatures != null, "signatures != null"); Debug.Assert(operationName != null, "operationName != null"); Expression[] args = new Expression[] { expr }; MethodBase method; if (this.FindMethod(signatures, "F", args, out method) != 1) { throw ParseError(Strings.RequestQueryParser_IncompatibleOperand(operationName, this.GetTypeName(args[0].Type), errorPos)); } expr = args[0]; } ///Checks that the operands (possibly promoted) are valid for the specified operation. /// Type with signatures to match. /// Name of operation for error reporting. /// Expression for left operand. /// Expression for right operand. /// Position for error reporting. private void CheckAndPromoteOperands(Type signatures, string operationName, ref Expression left, ref Expression right, int errorPos) { Expression[] args = new Expression[] { left, right }; MethodBase method; if (this.FindMethod(signatures, "F", args, out method) != 1) { throw this.IncompatibleOperandsError(operationName, left, right, errorPos); } left = args[0]; right = args[1]; } ///Finds a property in the specified type with the given name. /// Type in which to look for member. /// Member name. ///The ResourceProperty for the specified type. private ResourceProperty FindProperty(Type type, string memberName) { // This could also be a FindPropertyOrField method. Debug.Assert(type != null, "type != null"); Debug.Assert(memberName != null, "memberName != null"); ResourceProperty property = this.provider.TryResolvePropertyName(type, memberName); if (property != null) { if (property.ResourceContainer != null) { this.service.Configuration.CheckResourceRightsForRead(property.ResourceContainer, true /* singleResult */); } return property; } else { return null; } } ///Finds the named method in the specifid type. /// Type to look in. /// Name of method to look for. /// Arguments to method. /// Best method found. ///Number of matching methods. private int FindMethod(Type type, string methodName, Expression[] args, out MethodBase method) { BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Instance; foreach (Type t in SelfAndBaseTypes(type)) { MemberInfo[] members = t.FindMembers(MemberTypes.Method, flags, Type.FilterName, methodName); int count = this.FindBestMethod(members.Cast(), args, out method); if (count != 0) { return count; } } method = null; return 0; } /// Finds all applicable methods from the specified enumeration that match the arguments. /// Enumerable object of candidate methods. /// Argument expressions. ///Methods that apply to the specified arguments. private MethodData[] FindApplicableMethods(IEnumerablemethods, Expression[] args) { List result = new List (); foreach (MethodBase method in methods) { MethodData methodData = new MethodData(method, method.GetParameters()); if (this.IsApplicable(methodData, args)) { result.Add(methodData); } } return result.ToArray(); } /// Finds the best methods for the specified arguments given a candidate method enumeration. /// Enumerable object for candidate methods. /// Argument expressions to match. /// Best matched method. ///The number of "best match" methods. private int FindBestMethod(IEnumerablemethods, Expression[] args, out MethodBase method) { MethodData[] applicable = this.FindApplicableMethods(methods, args); if (applicable.Length > 1) { applicable = FindBestApplicableMethods(applicable, args); } int result = applicable.Length; method = null; if (applicable.Length == 1) { // If we started off with all non-OpenType expressions and end with all-OpenType // expressions, we've been too aggresive - the transition from non-open-types // to open types should initially happen only as a result of accessing open properties. #if ASTORIA_OPEN_OBJECT MethodData md = applicable[0]; bool originalArgsDefined = true; bool promotedArgsOpen = true; for (int i = 0; i < args.Length; i++) { originalArgsDefined = originalArgsDefined && !IsOpenPropertyExpression(args[i]); promotedArgsOpen = promotedArgsOpen && md.Parameters[i].ParameterType == typeof(object); args[i] = md.Args[i]; } method = (originalArgsDefined && promotedArgsOpen) ? null : md.MethodBase; result = (method == null) ? 0 : 1; #else for (int i = 0; i < args.Length; i++) { args[i] = applicable[0].Args[i]; } method = applicable[0].MethodBase; result = 1; #endif } else if (applicable.Length > 1) { // We may have the case for operators (which C# doesn't) in which we have a nullable operand // and a non-nullable operand. We choose to convert the one non-null operand to nullable in that // case (the binary expression will lift to null). if (args.Length == 2 && applicable.Length == 2 && GetNonNullableType(applicable[0].Parameters[0].ParameterType) == GetNonNullableType(applicable[1].Parameters[0].ParameterType)) { MethodData nullableMethod = TypeAllowsNull(applicable[0].Parameters[0].ParameterType) ? applicable[0] : applicable[1]; args[0] = nullableMethod.Args[0]; args[1] = nullableMethod.Args[1]; return this.FindBestMethod(methods, args, out method); } } return result; } /// Gets a name that can be used for error messages. /// Type to get name for. ///The name of the specified private string GetTypeName(Type type) { Debug.Assert(type != null, "type != null"); return WebUtil.GetTypeName(this.provider, type); } ///. Creates an exception indicated that two operands are incompatible. /// Name of operation for operands. /// Expression for left-hand operand. /// Expression for right-hand operand. /// Position for error. ///A new private Exception IncompatibleOperandsError(string operationName, Expression left, Expression right, int pos) { string message = Strings.RequestQueryParser_IncompatibleOperands( operationName, this.GetTypeName(left.Type), this.GetTypeName(right.Type), pos); return ParseError(message); } ///. Checks whether the specified method is applicable given the argument expressions. /// Method to check. /// Argument expressions. ///true if the method is applicable; false otherwise. private bool IsApplicable(MethodData method, Expression[] args) { if (method.Parameters.Length != args.Length) { return false; } Expression[] promotedArgs = new Expression[args.Length]; for (int i = 0; i < args.Length; i++) { ParameterInfo pi = method.Parameters[i]; Debug.Assert(!pi.IsOut, "!pi.IsOut"); Expression promoted = this.PromoteExpression(args[i], pi.ParameterType, false); if (promoted == null) { return false; } promotedArgs[i] = promoted; } method.Args = promotedArgs; return true; } ///Promotes the specified expression to the given type if necessary. /// Expression to promote. /// Type to change expression to. /// Whether an exact type is required; false implies a compatible type is OK. ///Expression with the promoted type. private Expression PromoteExpression(Expression expr, Type type, bool exact) { Debug.Assert(expr != null, "expr != null"); Debug.Assert(type != null, "type != null"); if (expr.Type == type) { return expr; } ConstantExpression ce = expr as ConstantExpression; if (ce != null) { if (ce == NullLiteral) { if (TypeAllowsNull(type)) { return Expression.Constant(null, type); } } else { string text; if (this.literals.TryGetValue(ce, out text)) { Type target = GetNonNullableType(type); object value = null; if (ce.Type == typeof(string) && target == typeof(Type)) { if (WebConvert.TryRemoveQuotes(ref text)) { ResourceType resourceType = this.provider.TryResolveTypeName(text); if (resourceType != null) { value = resourceType.Type; } } } else { switch (Type.GetTypeCode(ce.Type)) { case TypeCode.Int32: case TypeCode.Int64: value = ParseNumber(text, target); break; case TypeCode.Double: if (target == typeof(decimal)) { value = ParseNumber(text, target); } break; } } if (value != null) { return Expression.Constant(value, type); } } } } if (IsCompatibleWith(expr.Type, type)) { if (type.IsValueType || exact) { return Expression.Convert(expr, type); } return expr; } // Allow promotion from nullable to non-nullable by directly accessing underlying value. if (IsNullableType(expr.Type) && type.IsValueType) { Expression valueAccessExpression = Expression.Property(expr, "Value"); valueAccessExpression = this.PromoteExpression(valueAccessExpression, type, exact); return valueAccessExpression; } return null; } ///Checks that the current token has the specified identifier. /// Identifier to check. ///true if the current token is an identifier with the specified text. private bool TokenIdentifierIs(string id) { return this.CurrentToken.IdentifierIs(id); } ///Validates the current token is of the specified kind. /// Expected token kind. private void ValidateToken(TokenId t) { if (this.CurrentToken.Id != t) { throw ParseError(Strings.RequestQueryParser_SyntaxError(this.CurrentToken.Position)); } } #region Recursion control. ///Marks the fact that a recursive method was entered, and checks that the depth is allowed. private void RecurseEnter() { this.recursionDepth++; Debug.Assert(this.recursionDepth <= RecursionLimit, "this.recursionDepth <= recursionLimit"); if (this.recursionDepth == RecursionLimit) { throw DataServiceException.CreateDeepRecursion(RecursionLimit); } } ///Marks the fact that a recursive method is leaving.. private void RecurseLeave() { this.recursionDepth--; Debug.Assert(0 <= this.recursionDepth, "0 <= this.recursionDepth"); Debug.Assert(this.recursionDepth < RecursionLimit, "this.recursionDepth < recursionLimit"); } #endregion Recursion control. ///Use this class to encapsulate method information. [DebuggerDisplay("MethodData {methodBase}")] private class MethodData { #region Private fields. ///Described method. private readonly MethodBase methodBase; ///Parameters for method. private readonly ParameterInfo[] parameters; ///Argument expressions. private Expression[] args; #endregion Private fields. #region Constructors. ///Initializes a new /// Described method /// Parameters for method. public MethodData(MethodBase method, ParameterInfo[] parameters) { this.methodBase = method; this.parameters = parameters; } #endregion Constructors. #region Properties. ///instance. Argument expressions. public Expression[] Args { get { return this.args; } set { this.args = value; } } ///Described method. public MethodBase MethodBase { get { return this.methodBase; } } ///Parameters for method. public ParameterInfo[] Parameters { get { return this.parameters; } } ///Enumeration of parameter types. public IEnumerableParameterTypes { get { foreach (ParameterInfo parameter in this.Parameters) { yield return parameter.ParameterType; } } } #endregion Properties. } } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- BinaryExpression.cs
- DataGridViewCellCancelEventArgs.cs
- X509Certificate2Collection.cs
- CustomAttributeSerializer.cs
- ApplicationActivator.cs
- PersonalizationStateInfo.cs
- SpecularMaterial.cs
- HttpCookiesSection.cs
- CheckBoxStandardAdapter.cs
- VisualTarget.cs
- CellQuery.cs
- XmlTextAttribute.cs
- ArgumentOutOfRangeException.cs
- SRGSCompiler.cs
- ValidationError.cs
- TraceHandler.cs
- IISUnsafeMethods.cs
- BitmapEffectvisualstate.cs
- Parameter.cs
- ToolStripRenderer.cs
- EventManager.cs
- UrlAuthorizationModule.cs
- RijndaelManaged.cs
- BinaryHeap.cs
- RelatedCurrencyManager.cs
- CompositionAdorner.cs
- OdbcError.cs
- URL.cs
- ModelTypeConverter.cs
- SocketAddress.cs
- InvalidDataException.cs
- FontFamily.cs
- KnownBoxes.cs
- AutoSizeComboBox.cs
- SerializationObjectManager.cs
- DivideByZeroException.cs
- ExternalException.cs
- WindowsPen.cs
- PersonalizationEntry.cs
- GridEntryCollection.cs
- TextStore.cs
- Enum.cs
- OutOfMemoryException.cs
- DataGridViewDataConnection.cs
- SqlClientPermission.cs
- StylusPlugInCollection.cs
- TagNameToTypeMapper.cs
- _ConnectionGroup.cs
- GeometryModel3D.cs
- Soap.cs
- _AutoWebProxyScriptHelper.cs
- WorkflowInstance.cs
- ContentType.cs
- DocComment.cs
- DataRelationCollection.cs
- StyleTypedPropertyAttribute.cs
- TimeSpanValidatorAttribute.cs
- EnumValidator.cs
- TextTreeUndo.cs
- IImplicitResourceProvider.cs
- RegexMatch.cs
- TdsRecordBufferSetter.cs
- messageonlyhwndwrapper.cs
- Rotation3DAnimation.cs
- PrintPreviewGraphics.cs
- TextAutomationPeer.cs
- ConnectionsZoneDesigner.cs
- WebServiceData.cs
- SuppressMessageAttribute.cs
- AsyncCodeActivityContext.cs
- ThemeDictionaryExtension.cs
- CodeNamespace.cs
- TextEditorTables.cs
- PopupRoot.cs
- ConfigXmlSignificantWhitespace.cs
- WindowClosedEventArgs.cs
- MarshalDirectiveException.cs
- CharConverter.cs
- CodePageUtils.cs
- RequiredFieldValidator.cs
- ValidationSummary.cs
- DtrList.cs
- DictionaryItemsCollection.cs
- WebDisplayNameAttribute.cs
- PhonemeConverter.cs
- Win32Native.cs
- MSAAEventDispatcher.cs
- ObjectNotFoundException.cs
- MultipleViewPatternIdentifiers.cs
- DelegateBodyWriter.cs
- QueryLifecycle.cs
- DateTime.cs
- UniqueEventHelper.cs
- AutomationPatternInfo.cs
- PageThemeCodeDomTreeGenerator.cs
- Literal.cs
- DocumentGridContextMenu.cs
- SafeCertificateStore.cs
- FullTextLine.cs
- EmptyEnumerator.cs