Code:
/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / DataWeb / Server / System / Data / Services / Parsing / RequestQueryParser.cs / 1607349 / 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.Data.Services.Providers; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; #endregion Namespaces. ////// This class provides static methods to parse query options and compose /// them on an existing query. /// internal static class RequestQueryParser { #region Internal methods. ///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, OrderingInfo orderingInfo) { Debug.Assert(service != null, "service != null"); Debug.Assert(source != null, "source != null"); Debug.Assert(orderingInfo != null, "orderingInfo != null"); Expression queryExpr = source.Expression; string methodAsc = "OrderBy"; string methodDesc = "OrderByDescending"; foreach (OrderingExpression o in orderingInfo.OrderingExpressions) { LambdaExpression selectorLambda = (LambdaExpression)o.Expression; Type selectorType = selectorLambda.Body.Type; service.Provider.CheckIfOrderedType(selectorType); queryExpr = Expression.Call( typeof(Queryable), o.IsAscending ? methodAsc : methodDesc, new Type[] { source.ElementType, selectorType }, queryExpr, Expression.Quote(selectorLambda)); methodAsc = "ThenBy"; methodDesc = "ThenByDescending"; } return source.Provider.CreateQuery(queryExpr); } ///Filters a query like a SQL WHERE clause does. /// Service with data and configuration. /// Set for each item in the query if they are entities /// Type for each item in the query in Astoria metadata terms /// Original source for query. /// Predicate to compose. ///The composed query. internal static IQueryable Where(IDataService service, ResourceSetWrapper setForIt, ResourceType typeForIt, IQueryable source, string predicate) { Debug.Assert(service != null, "service != null"); Debug.Assert(typeForIt != null, "typeForIt != null"); Debug.Assert(typeForIt.ResourceTypeKind != ResourceTypeKind.EntityType || setForIt != null, "setForIt cannot be null if typeForIt is an entity type."); Debug.Assert(source != null, "source != null"); Debug.Assert(predicate != null, "predicate != null"); LambdaExpression lambda = ParseLambdaForWhere(service, setForIt, typeForIt, source.ElementType, predicate); 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. /// Resource set for "it" contextual variable. /// Type for "it" contextual variable. /// Actual (clr) element type for the sequence /// Expression to parse. ///The parsed expression. private static LambdaExpression ParseLambdaForWhere(IDataService service, ResourceSetWrapper setForIt, ResourceType typeForIt, Type queryElementType, string expression) { Debug.Assert(service != null, "service != null"); Debug.Assert(typeForIt != null, "typeForIt != null"); Debug.Assert(typeForIt.ResourceTypeKind != ResourceTypeKind.EntityType || setForIt != null, "setForIt cannot be null if typeForIt is an entity type."); Debug.Assert(queryElementType != null, "queryElementType != null"); Debug.Assert(expression != null, "expression != null"); Debug.Assert(typeForIt.InstanceType == queryElementType, "typeForIt.InstanceType == queryElementType"); ParameterExpression parameterForIt = Expression.Parameter(queryElementType, "it"); ExpressionParser parser = new ExpressionParser(service, setForIt, typeForIt, parameterForIt, expression); return Expression.Lambda(parser.ParseWhere(), parameterForIt); } #endregion Private methods. ///Use this class to parse an expression in the Astoria URI format. [DebuggerDisplay("ExpressionParser ({lexer.text})")] internal 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(); /// Method info for string comparison private static readonly MethodInfo StringCompareMethodInfo = typeof(DataServiceProviderMethods) .GetMethods(BindingFlags.Public | BindingFlags.Static) .Single(m => m.Name == "Compare" && m.GetParameters()[0].ParameterType == typeof(string)); ///Method info for Bool comparison private static readonly MethodInfo BoolCompareMethodInfo = typeof(DataServiceProviderMethods) .GetMethods(BindingFlags.Public | BindingFlags.Static) .Single(m => m.Name == "Compare" && m.GetParameters()[0].ParameterType == typeof(bool)); ///Method info for Bool? comparison private static readonly MethodInfo BoolCompareMethodInfoNullable = typeof(DataServiceProviderMethods) .GetMethods(BindingFlags.Public | BindingFlags.Static) .Single(m => m.Name == "Compare" && m.GetParameters()[0].ParameterType == typeof(bool?)); ///Method info for Guid comparison private static readonly MethodInfo GuidCompareMethodInfo = typeof(DataServiceProviderMethods) .GetMethods(BindingFlags.Public | BindingFlags.Static) .Single(m => m.Name == "Compare" && m.GetParameters()[0].ParameterType == typeof(Guid)); ///Method info for Guid? comparison private static readonly MethodInfo GuidCompareMethodInfoNullable = typeof(DataServiceProviderMethods) .GetMethods(BindingFlags.Public | BindingFlags.Static) .Single(m => m.Name == "Compare" && m.GetParameters()[0].ParameterType == typeof(Guid?)); ///Method info for byte array comparison. private static readonly MethodInfo ByteArrayEqualMethod = typeof(ExpressionParser) .GetMethod("ByteArraysEqual", BindingFlags.NonPublic | BindingFlags.Static); ///Method info for byte array comparison. private static readonly MethodInfo ByteArrayNotEqualMethod = typeof(ExpressionParser) .GetMethod("ByteArraysNotEqual", BindingFlags.NonPublic | BindingFlags.Static); ///Provider of data and metadata. private readonly DataServiceProviderWrapper provider; ///Service with data and configuration. private readonly IDataService service; ///Resource set for "it" private readonly ResourceSetWrapper setForIt; ///Metadata type for "it" private readonly ResourceType typeForIt; ///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; ///ResourceType and ResourceSet information for current segment. private SegmentTypeInfo currentSegmentInfo; #endregion Fields. #region Constructors. ///Initializes a new /// Service with data and configuration. /// Resource set for "it" contextual variable /// Type for "it" contextual variable /// Parameters for the current "it" context. /// Expression to parse. internal ExpressionParser(IDataService service, ResourceSetWrapper setForIt, ResourceType typeForIt, ParameterExpression parameterForIt, string expression) { Debug.Assert(service != null, "service != null"); // For Open types, we could potentially have typeForIt parameter set to null value, // However, we have decided not support ordering on Open properties which also implies // that we can not have entity typed properties on Open types Debug.Assert(typeForIt != null, "typeForIt != 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. (ReferenceEqualityComparer .Instance); this.setForIt = setForIt; this.typeForIt = typeForIt; 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(); expr = PrepareExpressionForWhere(expr); if (expr.Type != typeof(bool)) { throw ParseError(Strings.RequestQueryParser_ExpressionTypeMismatch(WebUtil.GetTypeName(typeof(bool)), exprPos)); } this.lexer.ValidateToken(TokenId.End); return expr; } ///Makes the expression that is used as a filter corresponding to skip token. /// Ordering expression. /// The provided skip token. ///LambdaExpression corresponding to the skip token filter. internal LambdaExpression BuildSkipTokenFilter(OrderingInfo topLevelOrderingInfo, KeyInstance k) { ParameterExpression element = Expression.Parameter(this.typeForIt.InstanceType, "element"); Expression lastCondition = Expression.Constant(true, typeof(bool)); Expression lastMatch = Expression.Constant(false, typeof(bool)); foreach (var v in WebUtil.Zip(topLevelOrderingInfo.OrderingExpressions, k.PositionalValues, (x, y) => new { Order = x, Value = y })) { Token comparisonExp = v.Order.IsAscending ? Token.GreaterThan : Token.LessThan; Expression fixedLambda = System.Data.Services.Client.ParameterReplacerVisitor.Replace( ((LambdaExpression)v.Order.Expression).Body, ((LambdaExpression)v.Order.Expression).Parameters[0], element); Expression comparison = GenerateLogicalAnd( lastCondition, this.GenerateNullAwareComparison(fixedLambda, (String)v.Value, comparisonExp)); lastMatch = GenerateLogicalOr(lastMatch, comparison); lastCondition = GenerateLogicalAnd( lastCondition, this.GenerateComparison(fixedLambda, (String)v.Value, Token.EqualsTo)); } lastMatch = PrepareExpressionForWhere(lastMatch); Debug.Assert(lastMatch.Type == typeof(bool), "Skip token should generate boolean expression."); return Expression.Lambda(lastMatch, element); } ///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 OrderingExpression(expr, ascending)); if (this.CurrentToken.Id != TokenId.Comma) { break; } this.lexer.NextToken(); } this.ValidateToken(TokenId.End); return orderings; } /// Parse one of the literals of skip token. /// Input literal. ///Object resulting from conversion of literal. internal object ParseSkipTokenLiteral(String literal) { ExpressionLexer l = new ExpressionLexer(literal); Expression e = this.ParsePrimaryStart(l); if (e.NodeType != ExpressionType.Constant) { throw new InvalidOperationException(Strings.RequsetQueryParser_ExpectingLiteralInSkipToken(literal)); } return ((ConstantExpression)e).Value; } ///Prepare the given expression for passing as a filter to Queryable.Where. /// Input expression. ///Expression converted to boolean expression. private static Expression PrepareExpressionForWhere(Expression expr) { if (IsOpenPropertyExpression(expr)) { expr = OpenTypeMethods.EqualExpression(expr, Expression.Constant(true, typeof(object))); expr = Expression.Convert(expr, typeof(bool)); } else if (WebUtil.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")); } return expr; } ///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 (target == typeof(object)) { return true; } 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 = WebUtil.IsNullableType(source); bool typeNullableA = WebUtil.IsNullableType(targetA); bool typeNullableB = WebUtil.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 (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.EqualExpression(left, right); } if (left.Type == typeof(byte[])) { return Expression.Equal(left, right, false, ExpressionParser.ByteArrayEqualMethod); } 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 (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.NotEqualExpression(left, right); } if (left.Type == typeof(byte[])) { return Expression.NotEqual(left, right, false, ExpressionParser.ByteArrayNotEqualMethod); } return Expression.NotEqual(left, right); } ///Generates a GreaterThan comparison expression. /// Left expression. /// Right expression. /// MethodInfo for comparison method used for string, bool, guid types ///The generated expression. private static Expression GenerateGreaterThan(Expression left, Expression right, MethodInfo comparisonMethodInfo) { if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.GreaterThanExpression(left, right); } if (comparisonMethodInfo != null) { left = Expression.Call(null, comparisonMethodInfo, left, right); right = Expression.Constant(0, typeof(int)); } return Expression.GreaterThan(left, right); } ///Generates a GreaterThanOrEqual comparsion expression. /// Left expression. /// Right expression. /// MethodInfo for comparison method used for string, bool, guid types ///The generated expression. private static Expression GenerateGreaterThanEqual(Expression left, Expression right, MethodInfo comparisonMethodInfo) { if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.GreaterThanOrEqualExpression(left, right); } if (comparisonMethodInfo != null) { left = Expression.Call(null, comparisonMethodInfo, left, right); right = Expression.Constant(0, typeof(int)); } return Expression.GreaterThanOrEqual(left, right); } ///Generates a LessThan comparsion expression. /// Left expression. /// Right expression. /// MethodInfo for comparison method used for string, bool, guid types ///The generated expression. private static Expression GenerateLessThan(Expression left, Expression right, MethodInfo comparisonMethodInfo) { if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.LessThanExpression(left, right); } if (comparisonMethodInfo != null) { left = Expression.Call(null, comparisonMethodInfo, left, right); right = Expression.Constant(0, typeof(int)); } return Expression.LessThan(left, right); } ///Generates a LessThanOrEqual comparsion expression. /// Left expression. /// Right expression. /// MethodInfo for comparison method used for string, bool, guid types ///The generated expression. private static Expression GenerateLessThanEqual(Expression left, Expression right, MethodInfo comparisonMethodInfo) { if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.LessThanOrEqualExpression(left, right); } if (comparisonMethodInfo != null) { left = Expression.Call(null, comparisonMethodInfo, left, right); right = Expression.Constant(0, typeof(int)); } return Expression.LessThanOrEqual(left, right); } ////// Compares 2 strings by ordinal, used to obtain MethodInfo for comparison operator expression parameter /// /// Left Parameter /// Right Parameter ///0 for equality, -1 for left less than right, 1 for left greater than right ////// Do not change the name of this function because LINQ to SQL is sensitive about the /// method name, so is EF probably. /// private static int Compare(String left, String right) { return Comparer.Default.Compare(left, right); } /// /// Compares 2 booleans with true greater than false, used to obtain MethodInfo for comparison operator expression parameter /// /// Left Parameter /// Right Parameter ///0 for equality, -1 for left less than right, 1 for left greater than right ////// Do not change the name of this function because LINQ to SQL is sensitive about the /// method name, so is EF probably. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification = "Need implementation")] private static int Compare(bool left, bool right) { return Comparer.Default.Compare(left, right); } /// /// Compares 2 nullable booleans with true greater than false, used to obtain MethodInfo for comparison operator expression parameter /// /// Left Parameter /// Right Parameter ///0 for equality, -1 for left less than right, 1 for left greater than right ////// Do not change the name of this function because LINQ to SQL is sensitive about the /// method name, so is EF probably. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification = "Need implementation")] private static int Compare(bool? left, bool? right) { return Comparer.Default.Compare(left, right); } /// /// Compares 2 guids by byte order, used to obtain MethodInfo for comparison operator expression parameter /// /// Left Parameter /// Right Parameter ///0 for equality, -1 for left less than right, 1 for left greater than right ////// Do not change the name of this function because LINQ to SQL is sensitive about the /// method name, so is EF probably. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification = "Need implementation")] private static int Compare(Guid left, Guid right) { return Comparer.Default.Compare(left, right); } /// /// Compares 2 nullable guids by byte order, used to obtain MethodInfo for comparison operator expression parameter /// /// Left Parameter /// Right Parameter ///0 for equality, -1 for left less than right, 1 for left greater than right ////// Do not change the name of this function because LINQ to SQL is sensitive about the /// method name, so is EF probably. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification = "Need implementation")] private static int Compare(Guid? left, Guid? right) { return Comparer.Default.Compare(left, right); } /// Generates an addition expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateAdd(Expression left, Expression right) { if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.AddExpression(left, right); } 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 (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.SubtractExpression(left, right); } return Expression.Subtract(left, right); } ///Generates a multiplication expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateMultiply(Expression left, Expression right) { if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.MultiplyExpression(left, right); } return Expression.Multiply(left, right); } ///Generates a negative of expression. /// Input expression. ///The generated expression. private static Expression GenerateNegate(Expression expr) { if (IsOpenPropertyExpression(expr)) { return OpenTypeMethods.NegateExpression(expr); } return Expression.Negate(expr); } ///Generates a not of expression. /// Input expression. ///The generated expression. private static Expression GenerateNot(Expression expr) { if (IsOpenPropertyExpression(expr)) { return OpenTypeMethods.NotExpression(expr); } if (expr.Type == typeof(bool) || expr.Type == typeof(Nullable)) { // Expression.Not will take numerics and apply '~' to them, thus the extra check here. return Expression.Not(expr); } else { throw ParseError(Strings.RequestQueryParser_NotDoesNotSupportType(expr.Type)); } } /// Generates a divide expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateDivide(Expression left, Expression right) { if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.DivideExpression(left, right); } return Expression.Divide(left, right); } ///Generates a modulo expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateModulo(Expression left, Expression right) { if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.ModuloExpression(left, right); } return Expression.Modulo(left, right); } ///Generates a logical And expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateLogicalAnd(Expression left, Expression right) { if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.AndAlsoExpression(left, right); } return Expression.AndAlso(left, right); } ///Generates a Logical Or expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateLogicalOr(Expression left, Expression right) { if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.OrElseExpression(left, right); } return Expression.OrElse(left, right); } ////// 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 != WebUtil.NullLiteral && expression.Type == typeof(object) && IsOpenExpression(expression); } ///is based on an open property; false otherwise. Checks if the given input expression refers to open types. /// Input expression. ///true if the input is an open expression, false otherwise. private static bool IsOpenExpression(Expression input) { Debug.Assert(input != null, "input != null"); input = StripObjectConvert(input); switch (input.NodeType) { case ExpressionType.Call: return ((MethodCallExpression)input).Method.DeclaringType == typeof(OpenTypeMethods); case ExpressionType.Negate: case ExpressionType.Not: return ((UnaryExpression)input).Method != null && ((UnaryExpression)input).Method.DeclaringType == typeof(OpenTypeMethods); case ExpressionType.Add: case ExpressionType.AndAlso: case ExpressionType.Divide: case ExpressionType.Equal: case ExpressionType.GreaterThan: case ExpressionType.GreaterThanOrEqual: case ExpressionType.LessThan: case ExpressionType.LessThanOrEqual: case ExpressionType.Modulo: case ExpressionType.Multiply: case ExpressionType.NotEqual: case ExpressionType.OrElse: case ExpressionType.Subtract: return ((BinaryExpression)input).Method != null && ((BinaryExpression)input).Method.DeclaringType == typeof(OpenTypeMethods); case ExpressionType.Conditional: // This handles that null propagation scenario. return IsOpenExpression(((ConditionalExpression)input).IfFalse); case ExpressionType.Convert: case ExpressionType.Constant: case ExpressionType.MemberAccess: case ExpressionType.TypeIs: return false; default: Debug.Assert(false, "Unexpected expression type found."); break; } return false; } ///Strips all Expression.Convert(object) calls from the input expression. /// Input expression. ///First non-Convert expression inside Converts that converts to non-object type. private static Expression StripObjectConvert(Expression input) { Debug.Assert(input != null, "input != null"); while (input.NodeType == ExpressionType.Convert && input.Type == typeof(object)) { UnaryExpression inner = (UnaryExpression)input; input = inner.Operand; } return input; } ///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); } ///is an identifier with the specified text. 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 static Exception IncompatibleOperandsError(string operationName, Expression left, Expression right, int pos) { string message = Strings.RequestQueryParser_IncompatibleOperands( operationName, WebUtil.GetTypeName(left.Type), WebUtil.GetTypeName(right.Type), pos); return ParseError(message); } #region Parsing. ///. Handles 'null' literals. /// Lexer to use for reading tokens ///The parsed expression. private static Expression ParseNullLiteral(ExpressionLexer l) { Debug.Assert(l.CurrentToken.Id == TokenId.NullLiteral, "l.CurrentToken.Id == TokenId.NullLiteral"); l.NextToken(); return WebUtil.NullLiteral; } ///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); left = GenerateLogicalOr(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); left = GenerateLogicalAnd(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(); left = this.GenerateComparisonExpression(left, right, op); } this.RecurseLeave(); return left; } ////// Generates a comparison expression given a left hand side expression and literal for right hand side /// /// Left hand side experssions /// Literal for right hand side /// gt, eq or lt operator token ///Resulting comparison expression private Expression GenerateComparison(Expression left, String rightLiteral, Token op) { ExpressionLexer l = new ExpressionLexer(rightLiteral); return this.GenerateComparisonExpression(left, this.ParsePrimaryStart(l), op); } ////// Generates a comparison expression which can handle NULL values for any type. /// NULL is always treated as the smallest possible value. /// So for example for strings NULL is smaller than any non-NULL string. /// For now only GreaterThan and LessThan operators are supported by this method. /// /// Left hand side expression /// Literal for the right hand side /// gt or lt operator token ///Resulting comparison expression (has a Boolean value) private Expression GenerateNullAwareComparison(Expression left, String rightLiteral, Token op) { Debug.Assert( op.Text == ExpressionConstants.KeywordGreaterThan || op.Text == ExpressionConstants.KeywordLessThan, "Only GreaterThan or LessThan operators are supported by the GenerateNullAwateComparison method for now."); ExpressionLexer l = new ExpressionLexer(rightLiteral); Expression right = this.ParsePrimaryStart(l); if (WebUtil.TypeAllowsNull(left.Type)) { if (!WebUtil.TypeAllowsNull(right.Type)) { right = Expression.Convert(right, typeof(Nullable<>).MakeGenericType(right.Type)); } } else if (WebUtil.TypeAllowsNull(right.Type)) { left = Expression.Convert(left, typeof(Nullable<>).MakeGenericType(left.Type)); } else { // Can't perform NULL aware comparison on this type. Just let the normal // comparison deal with it. Since the type doesn't allow NULL one should // never appear, so normal comparison is just fine. return this.GenerateComparisonExpression(left, right, op); } switch (op.Text) { case ExpressionConstants.KeywordGreaterThan: // (left != null) && ((right == null) || Compare(left, right) > 0) if (left == WebUtil.NullLiteral) { return Expression.Constant(false, typeof(bool)); } else if (right == WebUtil.NullLiteral) { return GenerateNotEqual(left, Expression.Constant(null, left.Type)); } else { return GenerateLogicalAnd( GenerateNotEqual(left, Expression.Constant(null, left.Type)), GenerateLogicalOr( GenerateEqual(right, Expression.Constant(null, right.Type)), this.GenerateComparisonExpression(left, right, op))); } case ExpressionConstants.KeywordLessThan: // (right != null) && ((left == null) || Compare(left, right) < 0) if (right == WebUtil.NullLiteral) { return Expression.Constant(false, typeof(bool)); } else if (left == WebUtil.NullLiteral) { return GenerateNotEqual(right, Expression.Constant(null, right.Type)); } else { return GenerateLogicalAnd( GenerateNotEqual(right, Expression.Constant(null, left.Type)), GenerateLogicalOr( GenerateEqual(left, Expression.Constant(null, right.Type)), this.GenerateComparisonExpression(left, right, op))); } default: // For now only < and > are supported as we use this only from $skiptoken throw ParseError( Strings.RequestQueryParser_NullOperatorUnsupported(op.Text, op.Position, this.lexer.ExpressionText)); } } ////// Given left and right hand side expressions, generates a comparison expression based /// on the given comparison token /// /// Left hand side expression /// Right hand side expression /// Comparison operator ///Resulting comparison expression private Expression GenerateComparisonExpression(Expression left, Expression right, Token op) { bool equality = op.IsEqualityOperator; if (equality && !left.Type.IsValueType && !right.Type.IsValueType) { if (left.Type != right.Type) { if (WebUtil.IsNullConstant(left)) { left = Expression.Constant(null, right.Type); } else if (WebUtil.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 ExpressionParser.IncompatibleOperandsError(op.Text, left, right, op.Position); } } } else if (left == WebUtil.NullLiteral || right == WebUtil.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 (!WebUtil.TypeAllowsNull(left.Type)) { left = Expression.Convert(left, typeof(Nullable<>).MakeGenericType(left.Type)); } else if (!WebUtil.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"); MethodInfo comparisonMethodInfo = null; if (!equality) { if (left.Type == typeof(string)) { comparisonMethodInfo = StringCompareMethodInfo; } else if (left.Type == typeof(bool)) { comparisonMethodInfo = BoolCompareMethodInfo; } else if (left.Type == typeof(bool?)) { comparisonMethodInfo = BoolCompareMethodInfoNullable; } else if (left.Type == typeof(Guid)) { comparisonMethodInfo = GuidCompareMethodInfo; } else if (left.Type == typeof(Guid?)) { comparisonMethodInfo = GuidCompareMethodInfoNullable; } } 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, comparisonMethodInfo); break; case ExpressionConstants.KeywordGreaterThanOrEqual: left = GenerateGreaterThanEqual(left, right, comparisonMethodInfo); break; case ExpressionConstants.KeywordLessThan: left = GenerateLessThan(left, right, comparisonMethodInfo); break; case ExpressionConstants.KeywordLessThanOrEqual: left = GenerateLessThanEqual(left, right, comparisonMethodInfo); break; } 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)) { left = GenerateMultiply(left, right); } else if (op.IdentifierIs(ExpressionConstants.KeywordDivide)) { left = GenerateDivide(left, right); } else { Debug.Assert(op.IdentifierIs(ExpressionConstants.KeywordModulo), "op.IdentifierIs(ExpressionConstants.KeywordModulo)"); left = GenerateModulo(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); expr = GenerateNegate(expr); } else { this.CheckAndPromoteOperand(typeof(OperationSignatures.INotSignatures), op.Text, ref expr, op.Position); expr = GenerateNot(expr); } this.RecurseLeave(); return expr; } this.RecurseLeave(); return this.ParsePrimary(); } ///Handles primary expressions. ///The parsed expression. private Expression ParsePrimary() { this.RecurseEnter(); Expression expr = this.ParsePrimaryStart(this.lexer); 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. /// Lexer to use for reading tokens ///The parsed expression. private Expression ParsePrimaryStart(ExpressionLexer l) { switch (l.CurrentToken.Id) { case TokenId.BooleanLiteral: return this.ParseTypedLiteral(typeof(bool), XmlConstants.EdmBooleanTypeName, l); case TokenId.DateTimeLiteral: return this.ParseTypedLiteral(typeof(DateTime), XmlConstants.EdmDateTimeTypeName, l); case TokenId.DecimalLiteral: return this.ParseTypedLiteral(typeof(decimal), XmlConstants.EdmDecimalTypeName, l); case TokenId.NullLiteral: return ExpressionParser.ParseNullLiteral(l); case TokenId.Identifier: Debug.Assert(Object.ReferenceEquals(this.lexer, l), "Lexer should be the member lexer"); return this.ParseIdentifier(); case TokenId.StringLiteral: return this.ParseTypedLiteral(typeof(string), XmlConstants.EdmStringTypeName, l); case TokenId.Int64Literal: return this.ParseTypedLiteral(typeof(Int64), XmlConstants.EdmInt64TypeName, l); case TokenId.IntegerLiteral: return this.ParseTypedLiteral(typeof(Int32), XmlConstants.EdmInt32TypeName, l); case TokenId.DoubleLiteral: return this.ParseTypedLiteral(typeof(double), XmlConstants.EdmDoubleTypeName, l); case TokenId.SingleLiteral: return this.ParseTypedLiteral(typeof(Single), XmlConstants.EdmSingleTypeName, l); case TokenId.GuidLiteral: return this.ParseTypedLiteral(typeof(Guid), XmlConstants.EdmGuidTypeName, l); case TokenId.BinaryLiteral: return this.ParseTypedLiteral(typeof(byte[]), XmlConstants.EdmBinaryTypeName, l); case TokenId.OpenParen: Debug.Assert(Object.ReferenceEquals(this.lexer, l), "Lexer should be the member lexer"); return this.ParseParenExpression(); default: throw ParseError(Strings.RequestQueryParser_ExpressionExpected(l.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); // An open paren here would indicate calling a method in regular C# syntax. bool identifierIsFunction = this.lexer.PeekNextToken().Id == TokenId.OpenParen; if (identifierIsFunction) { return this.ParseIdentifierAsFunction(); } else { this.currentSegmentInfo = new SegmentTypeInfo(this.typeForIt, this.setForIt, false); 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(); Expression candidateTypeArgument = arguments.Length > 1 ? arguments[1] : arguments.Length > 0 ? arguments[0] : null; // check if the one of the parameters is of open type. If yes, then we need to invoke // LateBoundMethods for this function otherwise not. bool openParameters = false; if (!functionDescriptions[0].IsTypeCast && !functionDescriptions[0].IsTypeCheck) { foreach (Expression argument in arguments) { if (IsOpenPropertyExpression(argument)) { openParameters = true; break; } } } Expression result = null; FunctionDescription function = null; if (openParameters) { foreach (FunctionDescription functionDescription in functionDescriptions) { if (functionDescription.ParameterTypes.Length == arguments.Length) { function = functionDescription; break; } } Expression[] openArguments = new Expression[arguments.Length]; for (int i = 0; i < openArguments.Length; i++) { if (IsOpenPropertyExpression(arguments[i])) { openArguments[i] = arguments[i]; } else { openArguments[i] = Expression.Convert(arguments[i], typeof(object)); } } if (function == null) { string message = Strings.RequestQueryParser_NoApplicableFunction( functionToken.Text, functionToken.Position, FunctionDescription.BuildSignatureList(functionToken.Text, functionDescriptions)); throw ParseError(message); } result = function.InvokeOpenTypeMethod(openArguments); } else { 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."); if (typeExpression.Type == typeof(Type)) { Type castTargetType = (Type)((ConstantExpression)typeExpression).Value; if (!WebUtil.TypeAllowsNull(castTargetType)) { arguments[arguments.Length - 1] = Expression.Constant(typeof(Nullable<>).MakeGenericType(castTargetType)); } } } Expression[] finalArguments; if (function.ConversionFunction == FunctionDescription.BinaryIsOfResourceType || function.ConversionFunction == FunctionDescription.BinaryCastResourceType) { finalArguments = new Expression[arguments.Length + 1]; for (int i = 0; i < arguments.Length; i++) { finalArguments[i] = arguments[i]; } finalArguments[arguments.Length] = Expression.Constant(IsOpenPropertyExpression(arguments[0]), typeof(bool)); } else { finalArguments = arguments; } result = function.ConversionFunction(this.it, finalArguments); } if (this.nullPropagationRequired && !function.IsTypeCheck && !function.IsTypeCast) { 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); } } // type casts change the type of the "current" item, reflect this in parser state if (function.IsTypeCast) { Debug.Assert(candidateTypeArgument != null, "Should have failed to bind to the function if arguments don't match"); Debug.Assert(candidateTypeArgument.Type == typeof(string), "First argument to 'cast' should have been a string"); Debug.Assert(candidateTypeArgument.NodeType == ExpressionType.Constant, "Non-constant in type name for a cast?"); this.currentSegmentInfo = new SegmentTypeInfo( WebUtil.TryResolveResourceType(this.provider, (string)((ConstantExpression)candidateTypeArgument).Value), this.currentSegmentInfo != null ? this.currentSegmentInfo.ResourceSet : this.setForIt, this.currentSegmentInfo != null ? this.currentSegmentInfo.IsCollection : false); } 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. /// Lexer to use for reading tokens ///The constants expression produced by building the given literal. private Expression ParseTypedLiteral(Type targetType, string targetTypeName, ExpressionLexer l) { object targetValue; if (!WebConvert.TryKeyStringToPrimitive(l.CurrentToken.Text, targetType, out targetValue)) { string message = Strings.RequestQueryParser_UnrecognizedLiteral(targetTypeName, l.CurrentToken.Text, l.CurrentToken.Position); throw ParseError(message); } Expression result = this.CreateLiteral(targetValue, l.CurrentToken.Text); l.NextToken(); return result; } ///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(); // Disallow the member access for a collection property. Debug.Assert(this.currentSegmentInfo != null, "Must have initialized current segment information."); if (this.currentSegmentInfo.IsCollection) { throw ParseError(Strings.RequestQueryParser_UnknownProperty(id, WebUtil.GetTypeName(type), errorPos)); } // An open paren here would indicate calling a method in regular C# syntax. ResourceProperty property = this.currentSegmentInfo.ResourceType == null ? null : // null means the current segment was already an open type this.currentSegmentInfo.ResourceType.TryResolvePropertyName(id); ResourceSetWrapper container = this.currentSegmentInfo.ResourceSet == null || property == null || property.TypeKind != ResourceTypeKind.EntityType ? null : this.provider.GetContainer(this.currentSegmentInfo.ResourceSet, this.currentSegmentInfo.ResourceType, property); if (property != null) { if (property.TypeKind == ResourceTypeKind.EntityType && container == null) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidPropertyNameSpecified(id, this.currentSegmentInfo.ResourceType.FullName)); } // We have a strongly-type property. Expression propertyAccess; if (property.CanReflectOnInstanceTypeProperty) { propertyAccess = Expression.Property(instance, this.currentSegmentInfo.ResourceType.GetPropertyInfo(property)); } else { // object DataServiceProviderMethods.GetValue(object, ResourceProperty) propertyAccess = Expression.Call( null, /*instance*/ DataServiceProviderMethods.GetValueMethodInfo, instance, Expression.Constant(property)); propertyAccess = Expression.Convert(propertyAccess, property.Type); } Expression result = this.ConsiderNullPropagation(instance, propertyAccess); if (container != null) { bool singleResult = property.Kind == ResourcePropertyKind.ResourceReference; DataServiceConfiguration.CheckResourceRightsForRead(container, singleResult); Expression filter = DataServiceConfiguration.ComposeQueryInterceptors(this.service, container); 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); } } this.currentSegmentInfo = new SegmentTypeInfo( property.ResourceType, container, property.Kind == ResourcePropertyKind.ResourceSetReference); return result; } else { ResourceType resourceType = this.currentSegmentInfo.ResourceType; this.currentSegmentInfo = new SegmentTypeInfo(null, null, false); // Open types can have any members inside them if (resourceType == null || resourceType.IsOpenType) { // object OpenTypeMethods.GetValue(object, string) Expression call = Expression.Call(null /* instance */, OpenTypeMethods.GetValueOpenPropertyMethodInfo, instance, Expression.Constant(id)); return this.ConsiderNullPropagation(instance, call); } else { throw ParseError(Strings.RequestQueryParser_UnknownProperty(id, WebUtil.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) { // DEVNOTE([....]): // The following code rely on Expression.Constant returning UNIQUE instances for every call to it // i.e., Expression.Constant(1) does not reference equals to Expression.Constant(1) 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 || !WebUtil.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 != WebUtil.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 (!WebUtil.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, WebUtil.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 ExpressionParser.IncompatibleOperandsError(operationName, left, right, errorPos); } left = args[0]; right = args[1]; } ///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. 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 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 = WebUtil.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; } /// 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 == WebUtil.NullLiteral) { if (WebUtil.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) || target == typeof(ResourceType))) { if (WebConvert.TryRemoveQuotes(ref text)) { ResourceType resourceType = WebUtil.TryResolveResourceType(this.provider, text); if (resourceType != null) { if (target == typeof(Type)) { if (resourceType.CanReflectOnInstanceType == true) { value = resourceType.InstanceType; } } else { if (resourceType.CanReflectOnInstanceType == false) { value = resourceType; } } } } } 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 (WebUtil.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() { WebUtil.RecurseEnterQueryParser(RecursionLimit, ref this.recursionDepth); } ///Marks the fact that a recursive method is leaving. private void RecurseLeave() { WebUtil.RecurseLeave(ref this.recursionDepth); } #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. } /// /// ResourceType and ResourceSet information for current segment. /// This is used only when parsing member access paths. /// private class SegmentTypeInfo { ///Constructor. /// Type for current segment. /// Set for current segment. /// Does current segment property refer to a collection. public SegmentTypeInfo(ResourceType resourceType, ResourceSetWrapper resourceSet, bool isCollection) { this.ResourceType = resourceType; this.ResourceSet = resourceSet; this.IsCollection = isCollection; } ///Type for the current segment. public ResourceType ResourceType { get; private set; } ///Resource set for the current segment. public ResourceSetWrapper ResourceSet { get; private set; } ///Does the current segment property refer to a collection. public bool IsCollection { get; private set; } } } } } // 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.Data.Services.Providers; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; #endregion Namespaces. ////// This class provides static methods to parse query options and compose /// them on an existing query. /// internal static class RequestQueryParser { #region Internal methods. ///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, OrderingInfo orderingInfo) { Debug.Assert(service != null, "service != null"); Debug.Assert(source != null, "source != null"); Debug.Assert(orderingInfo != null, "orderingInfo != null"); Expression queryExpr = source.Expression; string methodAsc = "OrderBy"; string methodDesc = "OrderByDescending"; foreach (OrderingExpression o in orderingInfo.OrderingExpressions) { LambdaExpression selectorLambda = (LambdaExpression)o.Expression; Type selectorType = selectorLambda.Body.Type; service.Provider.CheckIfOrderedType(selectorType); queryExpr = Expression.Call( typeof(Queryable), o.IsAscending ? methodAsc : methodDesc, new Type[] { source.ElementType, selectorType }, queryExpr, Expression.Quote(selectorLambda)); methodAsc = "ThenBy"; methodDesc = "ThenByDescending"; } return source.Provider.CreateQuery(queryExpr); } ///Filters a query like a SQL WHERE clause does. /// Service with data and configuration. /// Set for each item in the query if they are entities /// Type for each item in the query in Astoria metadata terms /// Original source for query. /// Predicate to compose. ///The composed query. internal static IQueryable Where(IDataService service, ResourceSetWrapper setForIt, ResourceType typeForIt, IQueryable source, string predicate) { Debug.Assert(service != null, "service != null"); Debug.Assert(typeForIt != null, "typeForIt != null"); Debug.Assert(typeForIt.ResourceTypeKind != ResourceTypeKind.EntityType || setForIt != null, "setForIt cannot be null if typeForIt is an entity type."); Debug.Assert(source != null, "source != null"); Debug.Assert(predicate != null, "predicate != null"); LambdaExpression lambda = ParseLambdaForWhere(service, setForIt, typeForIt, source.ElementType, predicate); 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. /// Resource set for "it" contextual variable. /// Type for "it" contextual variable. /// Actual (clr) element type for the sequence /// Expression to parse. ///The parsed expression. private static LambdaExpression ParseLambdaForWhere(IDataService service, ResourceSetWrapper setForIt, ResourceType typeForIt, Type queryElementType, string expression) { Debug.Assert(service != null, "service != null"); Debug.Assert(typeForIt != null, "typeForIt != null"); Debug.Assert(typeForIt.ResourceTypeKind != ResourceTypeKind.EntityType || setForIt != null, "setForIt cannot be null if typeForIt is an entity type."); Debug.Assert(queryElementType != null, "queryElementType != null"); Debug.Assert(expression != null, "expression != null"); Debug.Assert(typeForIt.InstanceType == queryElementType, "typeForIt.InstanceType == queryElementType"); ParameterExpression parameterForIt = Expression.Parameter(queryElementType, "it"); ExpressionParser parser = new ExpressionParser(service, setForIt, typeForIt, parameterForIt, expression); return Expression.Lambda(parser.ParseWhere(), parameterForIt); } #endregion Private methods. ///Use this class to parse an expression in the Astoria URI format. [DebuggerDisplay("ExpressionParser ({lexer.text})")] internal 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(); /// Method info for string comparison private static readonly MethodInfo StringCompareMethodInfo = typeof(DataServiceProviderMethods) .GetMethods(BindingFlags.Public | BindingFlags.Static) .Single(m => m.Name == "Compare" && m.GetParameters()[0].ParameterType == typeof(string)); ///Method info for Bool comparison private static readonly MethodInfo BoolCompareMethodInfo = typeof(DataServiceProviderMethods) .GetMethods(BindingFlags.Public | BindingFlags.Static) .Single(m => m.Name == "Compare" && m.GetParameters()[0].ParameterType == typeof(bool)); ///Method info for Bool? comparison private static readonly MethodInfo BoolCompareMethodInfoNullable = typeof(DataServiceProviderMethods) .GetMethods(BindingFlags.Public | BindingFlags.Static) .Single(m => m.Name == "Compare" && m.GetParameters()[0].ParameterType == typeof(bool?)); ///Method info for Guid comparison private static readonly MethodInfo GuidCompareMethodInfo = typeof(DataServiceProviderMethods) .GetMethods(BindingFlags.Public | BindingFlags.Static) .Single(m => m.Name == "Compare" && m.GetParameters()[0].ParameterType == typeof(Guid)); ///Method info for Guid? comparison private static readonly MethodInfo GuidCompareMethodInfoNullable = typeof(DataServiceProviderMethods) .GetMethods(BindingFlags.Public | BindingFlags.Static) .Single(m => m.Name == "Compare" && m.GetParameters()[0].ParameterType == typeof(Guid?)); ///Method info for byte array comparison. private static readonly MethodInfo ByteArrayEqualMethod = typeof(ExpressionParser) .GetMethod("ByteArraysEqual", BindingFlags.NonPublic | BindingFlags.Static); ///Method info for byte array comparison. private static readonly MethodInfo ByteArrayNotEqualMethod = typeof(ExpressionParser) .GetMethod("ByteArraysNotEqual", BindingFlags.NonPublic | BindingFlags.Static); ///Provider of data and metadata. private readonly DataServiceProviderWrapper provider; ///Service with data and configuration. private readonly IDataService service; ///Resource set for "it" private readonly ResourceSetWrapper setForIt; ///Metadata type for "it" private readonly ResourceType typeForIt; ///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; ///ResourceType and ResourceSet information for current segment. private SegmentTypeInfo currentSegmentInfo; #endregion Fields. #region Constructors. ///Initializes a new /// Service with data and configuration. /// Resource set for "it" contextual variable /// Type for "it" contextual variable /// Parameters for the current "it" context. /// Expression to parse. internal ExpressionParser(IDataService service, ResourceSetWrapper setForIt, ResourceType typeForIt, ParameterExpression parameterForIt, string expression) { Debug.Assert(service != null, "service != null"); // For Open types, we could potentially have typeForIt parameter set to null value, // However, we have decided not support ordering on Open properties which also implies // that we can not have entity typed properties on Open types Debug.Assert(typeForIt != null, "typeForIt != 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. (ReferenceEqualityComparer .Instance); this.setForIt = setForIt; this.typeForIt = typeForIt; 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(); expr = PrepareExpressionForWhere(expr); if (expr.Type != typeof(bool)) { throw ParseError(Strings.RequestQueryParser_ExpressionTypeMismatch(WebUtil.GetTypeName(typeof(bool)), exprPos)); } this.lexer.ValidateToken(TokenId.End); return expr; } ///Makes the expression that is used as a filter corresponding to skip token. /// Ordering expression. /// The provided skip token. ///LambdaExpression corresponding to the skip token filter. internal LambdaExpression BuildSkipTokenFilter(OrderingInfo topLevelOrderingInfo, KeyInstance k) { ParameterExpression element = Expression.Parameter(this.typeForIt.InstanceType, "element"); Expression lastCondition = Expression.Constant(true, typeof(bool)); Expression lastMatch = Expression.Constant(false, typeof(bool)); foreach (var v in WebUtil.Zip(topLevelOrderingInfo.OrderingExpressions, k.PositionalValues, (x, y) => new { Order = x, Value = y })) { Token comparisonExp = v.Order.IsAscending ? Token.GreaterThan : Token.LessThan; Expression fixedLambda = System.Data.Services.Client.ParameterReplacerVisitor.Replace( ((LambdaExpression)v.Order.Expression).Body, ((LambdaExpression)v.Order.Expression).Parameters[0], element); Expression comparison = GenerateLogicalAnd( lastCondition, this.GenerateNullAwareComparison(fixedLambda, (String)v.Value, comparisonExp)); lastMatch = GenerateLogicalOr(lastMatch, comparison); lastCondition = GenerateLogicalAnd( lastCondition, this.GenerateComparison(fixedLambda, (String)v.Value, Token.EqualsTo)); } lastMatch = PrepareExpressionForWhere(lastMatch); Debug.Assert(lastMatch.Type == typeof(bool), "Skip token should generate boolean expression."); return Expression.Lambda(lastMatch, element); } ///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 OrderingExpression(expr, ascending)); if (this.CurrentToken.Id != TokenId.Comma) { break; } this.lexer.NextToken(); } this.ValidateToken(TokenId.End); return orderings; } /// Parse one of the literals of skip token. /// Input literal. ///Object resulting from conversion of literal. internal object ParseSkipTokenLiteral(String literal) { ExpressionLexer l = new ExpressionLexer(literal); Expression e = this.ParsePrimaryStart(l); if (e.NodeType != ExpressionType.Constant) { throw new InvalidOperationException(Strings.RequsetQueryParser_ExpectingLiteralInSkipToken(literal)); } return ((ConstantExpression)e).Value; } ///Prepare the given expression for passing as a filter to Queryable.Where. /// Input expression. ///Expression converted to boolean expression. private static Expression PrepareExpressionForWhere(Expression expr) { if (IsOpenPropertyExpression(expr)) { expr = OpenTypeMethods.EqualExpression(expr, Expression.Constant(true, typeof(object))); expr = Expression.Convert(expr, typeof(bool)); } else if (WebUtil.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")); } return expr; } ///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 (target == typeof(object)) { return true; } 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 = WebUtil.IsNullableType(source); bool typeNullableA = WebUtil.IsNullableType(targetA); bool typeNullableB = WebUtil.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 (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.EqualExpression(left, right); } if (left.Type == typeof(byte[])) { return Expression.Equal(left, right, false, ExpressionParser.ByteArrayEqualMethod); } 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 (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.NotEqualExpression(left, right); } if (left.Type == typeof(byte[])) { return Expression.NotEqual(left, right, false, ExpressionParser.ByteArrayNotEqualMethod); } return Expression.NotEqual(left, right); } ///Generates a GreaterThan comparison expression. /// Left expression. /// Right expression. /// MethodInfo for comparison method used for string, bool, guid types ///The generated expression. private static Expression GenerateGreaterThan(Expression left, Expression right, MethodInfo comparisonMethodInfo) { if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.GreaterThanExpression(left, right); } if (comparisonMethodInfo != null) { left = Expression.Call(null, comparisonMethodInfo, left, right); right = Expression.Constant(0, typeof(int)); } return Expression.GreaterThan(left, right); } ///Generates a GreaterThanOrEqual comparsion expression. /// Left expression. /// Right expression. /// MethodInfo for comparison method used for string, bool, guid types ///The generated expression. private static Expression GenerateGreaterThanEqual(Expression left, Expression right, MethodInfo comparisonMethodInfo) { if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.GreaterThanOrEqualExpression(left, right); } if (comparisonMethodInfo != null) { left = Expression.Call(null, comparisonMethodInfo, left, right); right = Expression.Constant(0, typeof(int)); } return Expression.GreaterThanOrEqual(left, right); } ///Generates a LessThan comparsion expression. /// Left expression. /// Right expression. /// MethodInfo for comparison method used for string, bool, guid types ///The generated expression. private static Expression GenerateLessThan(Expression left, Expression right, MethodInfo comparisonMethodInfo) { if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.LessThanExpression(left, right); } if (comparisonMethodInfo != null) { left = Expression.Call(null, comparisonMethodInfo, left, right); right = Expression.Constant(0, typeof(int)); } return Expression.LessThan(left, right); } ///Generates a LessThanOrEqual comparsion expression. /// Left expression. /// Right expression. /// MethodInfo for comparison method used for string, bool, guid types ///The generated expression. private static Expression GenerateLessThanEqual(Expression left, Expression right, MethodInfo comparisonMethodInfo) { if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.LessThanOrEqualExpression(left, right); } if (comparisonMethodInfo != null) { left = Expression.Call(null, comparisonMethodInfo, left, right); right = Expression.Constant(0, typeof(int)); } return Expression.LessThanOrEqual(left, right); } ////// Compares 2 strings by ordinal, used to obtain MethodInfo for comparison operator expression parameter /// /// Left Parameter /// Right Parameter ///0 for equality, -1 for left less than right, 1 for left greater than right ////// Do not change the name of this function because LINQ to SQL is sensitive about the /// method name, so is EF probably. /// private static int Compare(String left, String right) { return Comparer.Default.Compare(left, right); } /// /// Compares 2 booleans with true greater than false, used to obtain MethodInfo for comparison operator expression parameter /// /// Left Parameter /// Right Parameter ///0 for equality, -1 for left less than right, 1 for left greater than right ////// Do not change the name of this function because LINQ to SQL is sensitive about the /// method name, so is EF probably. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification = "Need implementation")] private static int Compare(bool left, bool right) { return Comparer.Default.Compare(left, right); } /// /// Compares 2 nullable booleans with true greater than false, used to obtain MethodInfo for comparison operator expression parameter /// /// Left Parameter /// Right Parameter ///0 for equality, -1 for left less than right, 1 for left greater than right ////// Do not change the name of this function because LINQ to SQL is sensitive about the /// method name, so is EF probably. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification = "Need implementation")] private static int Compare(bool? left, bool? right) { return Comparer.Default.Compare(left, right); } /// /// Compares 2 guids by byte order, used to obtain MethodInfo for comparison operator expression parameter /// /// Left Parameter /// Right Parameter ///0 for equality, -1 for left less than right, 1 for left greater than right ////// Do not change the name of this function because LINQ to SQL is sensitive about the /// method name, so is EF probably. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification = "Need implementation")] private static int Compare(Guid left, Guid right) { return Comparer.Default.Compare(left, right); } /// /// Compares 2 nullable guids by byte order, used to obtain MethodInfo for comparison operator expression parameter /// /// Left Parameter /// Right Parameter ///0 for equality, -1 for left less than right, 1 for left greater than right ////// Do not change the name of this function because LINQ to SQL is sensitive about the /// method name, so is EF probably. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA908:AvoidTypesThatRequireJitCompilationInPrecompiledAssemblies", Justification = "Need implementation")] private static int Compare(Guid? left, Guid? right) { return Comparer.Default.Compare(left, right); } /// Generates an addition expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateAdd(Expression left, Expression right) { if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.AddExpression(left, right); } 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 (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.SubtractExpression(left, right); } return Expression.Subtract(left, right); } ///Generates a multiplication expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateMultiply(Expression left, Expression right) { if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.MultiplyExpression(left, right); } return Expression.Multiply(left, right); } ///Generates a negative of expression. /// Input expression. ///The generated expression. private static Expression GenerateNegate(Expression expr) { if (IsOpenPropertyExpression(expr)) { return OpenTypeMethods.NegateExpression(expr); } return Expression.Negate(expr); } ///Generates a not of expression. /// Input expression. ///The generated expression. private static Expression GenerateNot(Expression expr) { if (IsOpenPropertyExpression(expr)) { return OpenTypeMethods.NotExpression(expr); } if (expr.Type == typeof(bool) || expr.Type == typeof(Nullable)) { // Expression.Not will take numerics and apply '~' to them, thus the extra check here. return Expression.Not(expr); } else { throw ParseError(Strings.RequestQueryParser_NotDoesNotSupportType(expr.Type)); } } /// Generates a divide expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateDivide(Expression left, Expression right) { if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.DivideExpression(left, right); } return Expression.Divide(left, right); } ///Generates a modulo expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateModulo(Expression left, Expression right) { if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.ModuloExpression(left, right); } return Expression.Modulo(left, right); } ///Generates a logical And expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateLogicalAnd(Expression left, Expression right) { if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.AndAlsoExpression(left, right); } return Expression.AndAlso(left, right); } ///Generates a Logical Or expression. /// Left expression. /// Right expression. ///The generated expression. private static Expression GenerateLogicalOr(Expression left, Expression right) { if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right)) { return OpenTypeMethods.OrElseExpression(left, right); } return Expression.OrElse(left, right); } ////// 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 != WebUtil.NullLiteral && expression.Type == typeof(object) && IsOpenExpression(expression); } ///is based on an open property; false otherwise. Checks if the given input expression refers to open types. /// Input expression. ///true if the input is an open expression, false otherwise. private static bool IsOpenExpression(Expression input) { Debug.Assert(input != null, "input != null"); input = StripObjectConvert(input); switch (input.NodeType) { case ExpressionType.Call: return ((MethodCallExpression)input).Method.DeclaringType == typeof(OpenTypeMethods); case ExpressionType.Negate: case ExpressionType.Not: return ((UnaryExpression)input).Method != null && ((UnaryExpression)input).Method.DeclaringType == typeof(OpenTypeMethods); case ExpressionType.Add: case ExpressionType.AndAlso: case ExpressionType.Divide: case ExpressionType.Equal: case ExpressionType.GreaterThan: case ExpressionType.GreaterThanOrEqual: case ExpressionType.LessThan: case ExpressionType.LessThanOrEqual: case ExpressionType.Modulo: case ExpressionType.Multiply: case ExpressionType.NotEqual: case ExpressionType.OrElse: case ExpressionType.Subtract: return ((BinaryExpression)input).Method != null && ((BinaryExpression)input).Method.DeclaringType == typeof(OpenTypeMethods); case ExpressionType.Conditional: // This handles that null propagation scenario. return IsOpenExpression(((ConditionalExpression)input).IfFalse); case ExpressionType.Convert: case ExpressionType.Constant: case ExpressionType.MemberAccess: case ExpressionType.TypeIs: return false; default: Debug.Assert(false, "Unexpected expression type found."); break; } return false; } ///Strips all Expression.Convert(object) calls from the input expression. /// Input expression. ///First non-Convert expression inside Converts that converts to non-object type. private static Expression StripObjectConvert(Expression input) { Debug.Assert(input != null, "input != null"); while (input.NodeType == ExpressionType.Convert && input.Type == typeof(object)) { UnaryExpression inner = (UnaryExpression)input; input = inner.Operand; } return input; } ///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); } ///is an identifier with the specified text. 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 static Exception IncompatibleOperandsError(string operationName, Expression left, Expression right, int pos) { string message = Strings.RequestQueryParser_IncompatibleOperands( operationName, WebUtil.GetTypeName(left.Type), WebUtil.GetTypeName(right.Type), pos); return ParseError(message); } #region Parsing. ///. Handles 'null' literals. /// Lexer to use for reading tokens ///The parsed expression. private static Expression ParseNullLiteral(ExpressionLexer l) { Debug.Assert(l.CurrentToken.Id == TokenId.NullLiteral, "l.CurrentToken.Id == TokenId.NullLiteral"); l.NextToken(); return WebUtil.NullLiteral; } ///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); left = GenerateLogicalOr(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); left = GenerateLogicalAnd(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(); left = this.GenerateComparisonExpression(left, right, op); } this.RecurseLeave(); return left; } ////// Generates a comparison expression given a left hand side expression and literal for right hand side /// /// Left hand side experssions /// Literal for right hand side /// gt, eq or lt operator token ///Resulting comparison expression private Expression GenerateComparison(Expression left, String rightLiteral, Token op) { ExpressionLexer l = new ExpressionLexer(rightLiteral); return this.GenerateComparisonExpression(left, this.ParsePrimaryStart(l), op); } ////// Generates a comparison expression which can handle NULL values for any type. /// NULL is always treated as the smallest possible value. /// So for example for strings NULL is smaller than any non-NULL string. /// For now only GreaterThan and LessThan operators are supported by this method. /// /// Left hand side expression /// Literal for the right hand side /// gt or lt operator token ///Resulting comparison expression (has a Boolean value) private Expression GenerateNullAwareComparison(Expression left, String rightLiteral, Token op) { Debug.Assert( op.Text == ExpressionConstants.KeywordGreaterThan || op.Text == ExpressionConstants.KeywordLessThan, "Only GreaterThan or LessThan operators are supported by the GenerateNullAwateComparison method for now."); ExpressionLexer l = new ExpressionLexer(rightLiteral); Expression right = this.ParsePrimaryStart(l); if (WebUtil.TypeAllowsNull(left.Type)) { if (!WebUtil.TypeAllowsNull(right.Type)) { right = Expression.Convert(right, typeof(Nullable<>).MakeGenericType(right.Type)); } } else if (WebUtil.TypeAllowsNull(right.Type)) { left = Expression.Convert(left, typeof(Nullable<>).MakeGenericType(left.Type)); } else { // Can't perform NULL aware comparison on this type. Just let the normal // comparison deal with it. Since the type doesn't allow NULL one should // never appear, so normal comparison is just fine. return this.GenerateComparisonExpression(left, right, op); } switch (op.Text) { case ExpressionConstants.KeywordGreaterThan: // (left != null) && ((right == null) || Compare(left, right) > 0) if (left == WebUtil.NullLiteral) { return Expression.Constant(false, typeof(bool)); } else if (right == WebUtil.NullLiteral) { return GenerateNotEqual(left, Expression.Constant(null, left.Type)); } else { return GenerateLogicalAnd( GenerateNotEqual(left, Expression.Constant(null, left.Type)), GenerateLogicalOr( GenerateEqual(right, Expression.Constant(null, right.Type)), this.GenerateComparisonExpression(left, right, op))); } case ExpressionConstants.KeywordLessThan: // (right != null) && ((left == null) || Compare(left, right) < 0) if (right == WebUtil.NullLiteral) { return Expression.Constant(false, typeof(bool)); } else if (left == WebUtil.NullLiteral) { return GenerateNotEqual(right, Expression.Constant(null, right.Type)); } else { return GenerateLogicalAnd( GenerateNotEqual(right, Expression.Constant(null, left.Type)), GenerateLogicalOr( GenerateEqual(left, Expression.Constant(null, right.Type)), this.GenerateComparisonExpression(left, right, op))); } default: // For now only < and > are supported as we use this only from $skiptoken throw ParseError( Strings.RequestQueryParser_NullOperatorUnsupported(op.Text, op.Position, this.lexer.ExpressionText)); } } ////// Given left and right hand side expressions, generates a comparison expression based /// on the given comparison token /// /// Left hand side expression /// Right hand side expression /// Comparison operator ///Resulting comparison expression private Expression GenerateComparisonExpression(Expression left, Expression right, Token op) { bool equality = op.IsEqualityOperator; if (equality && !left.Type.IsValueType && !right.Type.IsValueType) { if (left.Type != right.Type) { if (WebUtil.IsNullConstant(left)) { left = Expression.Constant(null, right.Type); } else if (WebUtil.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 ExpressionParser.IncompatibleOperandsError(op.Text, left, right, op.Position); } } } else if (left == WebUtil.NullLiteral || right == WebUtil.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 (!WebUtil.TypeAllowsNull(left.Type)) { left = Expression.Convert(left, typeof(Nullable<>).MakeGenericType(left.Type)); } else if (!WebUtil.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"); MethodInfo comparisonMethodInfo = null; if (!equality) { if (left.Type == typeof(string)) { comparisonMethodInfo = StringCompareMethodInfo; } else if (left.Type == typeof(bool)) { comparisonMethodInfo = BoolCompareMethodInfo; } else if (left.Type == typeof(bool?)) { comparisonMethodInfo = BoolCompareMethodInfoNullable; } else if (left.Type == typeof(Guid)) { comparisonMethodInfo = GuidCompareMethodInfo; } else if (left.Type == typeof(Guid?)) { comparisonMethodInfo = GuidCompareMethodInfoNullable; } } 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, comparisonMethodInfo); break; case ExpressionConstants.KeywordGreaterThanOrEqual: left = GenerateGreaterThanEqual(left, right, comparisonMethodInfo); break; case ExpressionConstants.KeywordLessThan: left = GenerateLessThan(left, right, comparisonMethodInfo); break; case ExpressionConstants.KeywordLessThanOrEqual: left = GenerateLessThanEqual(left, right, comparisonMethodInfo); break; } 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)) { left = GenerateMultiply(left, right); } else if (op.IdentifierIs(ExpressionConstants.KeywordDivide)) { left = GenerateDivide(left, right); } else { Debug.Assert(op.IdentifierIs(ExpressionConstants.KeywordModulo), "op.IdentifierIs(ExpressionConstants.KeywordModulo)"); left = GenerateModulo(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); expr = GenerateNegate(expr); } else { this.CheckAndPromoteOperand(typeof(OperationSignatures.INotSignatures), op.Text, ref expr, op.Position); expr = GenerateNot(expr); } this.RecurseLeave(); return expr; } this.RecurseLeave(); return this.ParsePrimary(); } ///Handles primary expressions. ///The parsed expression. private Expression ParsePrimary() { this.RecurseEnter(); Expression expr = this.ParsePrimaryStart(this.lexer); 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. /// Lexer to use for reading tokens ///The parsed expression. private Expression ParsePrimaryStart(ExpressionLexer l) { switch (l.CurrentToken.Id) { case TokenId.BooleanLiteral: return this.ParseTypedLiteral(typeof(bool), XmlConstants.EdmBooleanTypeName, l); case TokenId.DateTimeLiteral: return this.ParseTypedLiteral(typeof(DateTime), XmlConstants.EdmDateTimeTypeName, l); case TokenId.DecimalLiteral: return this.ParseTypedLiteral(typeof(decimal), XmlConstants.EdmDecimalTypeName, l); case TokenId.NullLiteral: return ExpressionParser.ParseNullLiteral(l); case TokenId.Identifier: Debug.Assert(Object.ReferenceEquals(this.lexer, l), "Lexer should be the member lexer"); return this.ParseIdentifier(); case TokenId.StringLiteral: return this.ParseTypedLiteral(typeof(string), XmlConstants.EdmStringTypeName, l); case TokenId.Int64Literal: return this.ParseTypedLiteral(typeof(Int64), XmlConstants.EdmInt64TypeName, l); case TokenId.IntegerLiteral: return this.ParseTypedLiteral(typeof(Int32), XmlConstants.EdmInt32TypeName, l); case TokenId.DoubleLiteral: return this.ParseTypedLiteral(typeof(double), XmlConstants.EdmDoubleTypeName, l); case TokenId.SingleLiteral: return this.ParseTypedLiteral(typeof(Single), XmlConstants.EdmSingleTypeName, l); case TokenId.GuidLiteral: return this.ParseTypedLiteral(typeof(Guid), XmlConstants.EdmGuidTypeName, l); case TokenId.BinaryLiteral: return this.ParseTypedLiteral(typeof(byte[]), XmlConstants.EdmBinaryTypeName, l); case TokenId.OpenParen: Debug.Assert(Object.ReferenceEquals(this.lexer, l), "Lexer should be the member lexer"); return this.ParseParenExpression(); default: throw ParseError(Strings.RequestQueryParser_ExpressionExpected(l.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); // An open paren here would indicate calling a method in regular C# syntax. bool identifierIsFunction = this.lexer.PeekNextToken().Id == TokenId.OpenParen; if (identifierIsFunction) { return this.ParseIdentifierAsFunction(); } else { this.currentSegmentInfo = new SegmentTypeInfo(this.typeForIt, this.setForIt, false); 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(); Expression candidateTypeArgument = arguments.Length > 1 ? arguments[1] : arguments.Length > 0 ? arguments[0] : null; // check if the one of the parameters is of open type. If yes, then we need to invoke // LateBoundMethods for this function otherwise not. bool openParameters = false; if (!functionDescriptions[0].IsTypeCast && !functionDescriptions[0].IsTypeCheck) { foreach (Expression argument in arguments) { if (IsOpenPropertyExpression(argument)) { openParameters = true; break; } } } Expression result = null; FunctionDescription function = null; if (openParameters) { foreach (FunctionDescription functionDescription in functionDescriptions) { if (functionDescription.ParameterTypes.Length == arguments.Length) { function = functionDescription; break; } } Expression[] openArguments = new Expression[arguments.Length]; for (int i = 0; i < openArguments.Length; i++) { if (IsOpenPropertyExpression(arguments[i])) { openArguments[i] = arguments[i]; } else { openArguments[i] = Expression.Convert(arguments[i], typeof(object)); } } if (function == null) { string message = Strings.RequestQueryParser_NoApplicableFunction( functionToken.Text, functionToken.Position, FunctionDescription.BuildSignatureList(functionToken.Text, functionDescriptions)); throw ParseError(message); } result = function.InvokeOpenTypeMethod(openArguments); } else { 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."); if (typeExpression.Type == typeof(Type)) { Type castTargetType = (Type)((ConstantExpression)typeExpression).Value; if (!WebUtil.TypeAllowsNull(castTargetType)) { arguments[arguments.Length - 1] = Expression.Constant(typeof(Nullable<>).MakeGenericType(castTargetType)); } } } Expression[] finalArguments; if (function.ConversionFunction == FunctionDescription.BinaryIsOfResourceType || function.ConversionFunction == FunctionDescription.BinaryCastResourceType) { finalArguments = new Expression[arguments.Length + 1]; for (int i = 0; i < arguments.Length; i++) { finalArguments[i] = arguments[i]; } finalArguments[arguments.Length] = Expression.Constant(IsOpenPropertyExpression(arguments[0]), typeof(bool)); } else { finalArguments = arguments; } result = function.ConversionFunction(this.it, finalArguments); } if (this.nullPropagationRequired && !function.IsTypeCheck && !function.IsTypeCast) { 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); } } // type casts change the type of the "current" item, reflect this in parser state if (function.IsTypeCast) { Debug.Assert(candidateTypeArgument != null, "Should have failed to bind to the function if arguments don't match"); Debug.Assert(candidateTypeArgument.Type == typeof(string), "First argument to 'cast' should have been a string"); Debug.Assert(candidateTypeArgument.NodeType == ExpressionType.Constant, "Non-constant in type name for a cast?"); this.currentSegmentInfo = new SegmentTypeInfo( WebUtil.TryResolveResourceType(this.provider, (string)((ConstantExpression)candidateTypeArgument).Value), this.currentSegmentInfo != null ? this.currentSegmentInfo.ResourceSet : this.setForIt, this.currentSegmentInfo != null ? this.currentSegmentInfo.IsCollection : false); } 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. /// Lexer to use for reading tokens ///The constants expression produced by building the given literal. private Expression ParseTypedLiteral(Type targetType, string targetTypeName, ExpressionLexer l) { object targetValue; if (!WebConvert.TryKeyStringToPrimitive(l.CurrentToken.Text, targetType, out targetValue)) { string message = Strings.RequestQueryParser_UnrecognizedLiteral(targetTypeName, l.CurrentToken.Text, l.CurrentToken.Position); throw ParseError(message); } Expression result = this.CreateLiteral(targetValue, l.CurrentToken.Text); l.NextToken(); return result; } ///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(); // Disallow the member access for a collection property. Debug.Assert(this.currentSegmentInfo != null, "Must have initialized current segment information."); if (this.currentSegmentInfo.IsCollection) { throw ParseError(Strings.RequestQueryParser_UnknownProperty(id, WebUtil.GetTypeName(type), errorPos)); } // An open paren here would indicate calling a method in regular C# syntax. ResourceProperty property = this.currentSegmentInfo.ResourceType == null ? null : // null means the current segment was already an open type this.currentSegmentInfo.ResourceType.TryResolvePropertyName(id); ResourceSetWrapper container = this.currentSegmentInfo.ResourceSet == null || property == null || property.TypeKind != ResourceTypeKind.EntityType ? null : this.provider.GetContainer(this.currentSegmentInfo.ResourceSet, this.currentSegmentInfo.ResourceType, property); if (property != null) { if (property.TypeKind == ResourceTypeKind.EntityType && container == null) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidPropertyNameSpecified(id, this.currentSegmentInfo.ResourceType.FullName)); } // We have a strongly-type property. Expression propertyAccess; if (property.CanReflectOnInstanceTypeProperty) { propertyAccess = Expression.Property(instance, this.currentSegmentInfo.ResourceType.GetPropertyInfo(property)); } else { // object DataServiceProviderMethods.GetValue(object, ResourceProperty) propertyAccess = Expression.Call( null, /*instance*/ DataServiceProviderMethods.GetValueMethodInfo, instance, Expression.Constant(property)); propertyAccess = Expression.Convert(propertyAccess, property.Type); } Expression result = this.ConsiderNullPropagation(instance, propertyAccess); if (container != null) { bool singleResult = property.Kind == ResourcePropertyKind.ResourceReference; DataServiceConfiguration.CheckResourceRightsForRead(container, singleResult); Expression filter = DataServiceConfiguration.ComposeQueryInterceptors(this.service, container); 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); } } this.currentSegmentInfo = new SegmentTypeInfo( property.ResourceType, container, property.Kind == ResourcePropertyKind.ResourceSetReference); return result; } else { ResourceType resourceType = this.currentSegmentInfo.ResourceType; this.currentSegmentInfo = new SegmentTypeInfo(null, null, false); // Open types can have any members inside them if (resourceType == null || resourceType.IsOpenType) { // object OpenTypeMethods.GetValue(object, string) Expression call = Expression.Call(null /* instance */, OpenTypeMethods.GetValueOpenPropertyMethodInfo, instance, Expression.Constant(id)); return this.ConsiderNullPropagation(instance, call); } else { throw ParseError(Strings.RequestQueryParser_UnknownProperty(id, WebUtil.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) { // DEVNOTE([....]): // The following code rely on Expression.Constant returning UNIQUE instances for every call to it // i.e., Expression.Constant(1) does not reference equals to Expression.Constant(1) 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 || !WebUtil.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 != WebUtil.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 (!WebUtil.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, WebUtil.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 ExpressionParser.IncompatibleOperandsError(operationName, left, right, errorPos); } left = args[0]; right = args[1]; } ///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. 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 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 = WebUtil.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; } /// 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 == WebUtil.NullLiteral) { if (WebUtil.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) || target == typeof(ResourceType))) { if (WebConvert.TryRemoveQuotes(ref text)) { ResourceType resourceType = WebUtil.TryResolveResourceType(this.provider, text); if (resourceType != null) { if (target == typeof(Type)) { if (resourceType.CanReflectOnInstanceType == true) { value = resourceType.InstanceType; } } else { if (resourceType.CanReflectOnInstanceType == false) { value = resourceType; } } } } } 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 (WebUtil.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() { WebUtil.RecurseEnterQueryParser(RecursionLimit, ref this.recursionDepth); } ///Marks the fact that a recursive method is leaving. private void RecurseLeave() { WebUtil.RecurseLeave(ref this.recursionDepth); } #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. } /// /// ResourceType and ResourceSet information for current segment. /// This is used only when parsing member access paths. /// private class SegmentTypeInfo { ///Constructor. /// Type for current segment. /// Set for current segment. /// Does current segment property refer to a collection. public SegmentTypeInfo(ResourceType resourceType, ResourceSetWrapper resourceSet, bool isCollection) { this.ResourceType = resourceType; this.ResourceSet = resourceSet; this.IsCollection = isCollection; } ///Type for the current segment. public ResourceType ResourceType { get; private set; } ///Resource set for the current segment. public ResourceSetWrapper ResourceSet { get; private set; } ///Does the current segment property refer to a collection. public bool IsCollection { get; private set; } } } } } // 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
- _PooledStream.cs
- SqlMethodTransformer.cs
- ContainerAction.cs
- ClientTargetCollection.cs
- BindingContext.cs
- InputScope.cs
- _HeaderInfo.cs
- ADMembershipUser.cs
- LinqDataSourceInsertEventArgs.cs
- PrintingPermissionAttribute.cs
- ServiceNotStartedException.cs
- SqlFileStream.cs
- SkipStoryboardToFill.cs
- TransformGroup.cs
- ExpressionLexer.cs
- Adorner.cs
- ValidationResult.cs
- GrammarBuilderBase.cs
- X509SecurityTokenProvider.cs
- WindowsGrip.cs
- AsyncInvokeContext.cs
- ExtensionSimplifierMarkupObject.cs
- AnyAllSearchOperator.cs
- XmlSchemaSimpleContent.cs
- _NetworkingPerfCounters.cs
- TypeValidationEventArgs.cs
- VBIdentifierTrimConverter.cs
- CollectionsUtil.cs
- Vector3DAnimationUsingKeyFrames.cs
- InfiniteIntConverter.cs
- ModelFunction.cs
- HttpStaticObjectsCollectionBase.cs
- ResourcePool.cs
- CompilerError.cs
- XmlMembersMapping.cs
- TextServicesPropertyRanges.cs
- TableRow.cs
- HtmlImage.cs
- DataViewManager.cs
- Win32Native.cs
- ApplicationServicesHostFactory.cs
- RectAnimationClockResource.cs
- XmlObjectSerializerReadContext.cs
- PropagatorResult.cs
- NonSerializedAttribute.cs
- DragEvent.cs
- PrintControllerWithStatusDialog.cs
- ContravarianceAdapter.cs
- RC2CryptoServiceProvider.cs
- AbandonedMutexException.cs
- MessageSecurityVersionConverter.cs
- BufferedGraphicsManager.cs
- Point3DCollection.cs
- SEHException.cs
- AutomationElementIdentifiers.cs
- CodeBlockBuilder.cs
- Profiler.cs
- ZoneButton.cs
- X509InitiatorCertificateClientElement.cs
- PageStatePersister.cs
- AdornerLayer.cs
- DispatchWrapper.cs
- Stacktrace.cs
- SqlCacheDependencySection.cs
- ResourceContainer.cs
- StrongBox.cs
- RegexBoyerMoore.cs
- DifferencingCollection.cs
- HttpModuleCollection.cs
- BitmapFrame.cs
- BrowserCapabilitiesCompiler.cs
- DoubleLinkListEnumerator.cs
- XmlNodeChangedEventManager.cs
- ThicknessConverter.cs
- RunClient.cs
- Encoding.cs
- ObjectDataSourceFilteringEventArgs.cs
- GregorianCalendarHelper.cs
- DNS.cs
- InheritanceRules.cs
- SystemColorTracker.cs
- BindingSource.cs
- TransformerConfigurationWizardBase.cs
- EndPoint.cs
- EventArgs.cs
- LinqDataSourceContextData.cs
- XmlAnyAttributeAttribute.cs
- DocumentSchemaValidator.cs
- AtlasWeb.Designer.cs
- MachinePropertyVariants.cs
- PolicyException.cs
- _ProxyChain.cs
- DocumentXmlWriter.cs
- FragmentQueryKB.cs
- PrintDialog.cs
- PrtCap_Reader.cs
- X509ChainElement.cs
- InheritedPropertyDescriptor.cs
- TransactionBehavior.cs
- WebBrowserBase.cs