/// <summary>Sorts a query like a SQL ORDER BY clause does.</summary> /// <param name="source">Original source for query.</param> /// <param name="orderingInfo">Ordering definition to compose.</param> /// <returns>The composed query.</returns> internal static Expression OrderBy(Expression source, OrderingInfo orderingInfo) { Debug.Assert(source != null, "source != null"); Debug.Assert(orderingInfo != null, "orderingInfo != null"); Expression queryExpr = source; bool useOrderBy = true; foreach (OrderingExpression o in orderingInfo.OrderingExpressions) { LambdaExpression selectorLambda = (LambdaExpression)o.Expression; Type selectorType = selectorLambda.Body.Type; Debug.Assert(selectorType != null, "type != null"); // ensure either the expression type is orderable (ie, primitive) or its an open expression. if (!WebUtil.IsPrimitiveType(selectorType) && !OpenTypeMethods.IsOpenExpression(selectorLambda.Body)) { throw DataServiceException.CreateBadRequestError(Strings.RequestQueryParser_OrderByDoesNotSupportType(WebUtil.GetTypeName(selectorType))); } if (useOrderBy) { queryExpr = o.IsAscending ? queryExpr.QueryableOrderBy(selectorLambda) : queryExpr.QueryableOrderByDescending(selectorLambda); } else { queryExpr = o.IsAscending ? queryExpr.QueryableThenBy(selectorLambda) : queryExpr.QueryableThenByDescending(selectorLambda); } useOrderBy = false; } return(queryExpr); }
/// <summary>Parse one of the literals of skip token.</summary> /// <param name="literal">Input literal.</param> /// <returns>Object resulting from conversion of literal.</returns> internal static object ParseSkipTokenLiteral(string literal) { ExpressionLexer l = new ExpressionLexer(literal); ConstantNode node; if (!TokenToQueryNodeTranslator.TryCreateLiteral(l.CurrentToken, out node)) { throw new InvalidOperationException(ServiceStrings.RequsetQueryParser_ExpectingLiteralInSkipToken(literal)); } return(node.Value); }
/// <summary> /// Parses the given token into a constant node of the given target type. /// </summary> /// <param name="targetType">The tarket type.</param> /// <param name="targetTypeName">The target type name.</param> /// <param name="token">The token to parse.</param> /// <returns>The parsed constant node.</returns> private static ConstantNode ParseTypedLiteral(Type targetType, string targetTypeName, ExpressionToken token) { object literalValue; if (!LiteralParser.ForExpressions.TryParseLiteral(targetType, token.Text, out literalValue)) { string message = Strings.RequestQueryParser_UnrecognizedLiteral(targetTypeName, token.Text); throw DataServiceException.CreateSyntaxError(message); } return(new ConstantNode(literalValue, token.Text)); }
/// <summary> /// Finds the entity set that this navigation property refers to. /// </summary> /// <param name="navigationProperty">Instance of navigation property.</param> /// <returns>an instance of IEdmEntitySet that this navigation property refers to.</returns> public IEdmNavigationSource FindNavigationTarget(IEdmNavigationProperty navigationProperty) { WebUtil.CheckArgumentNull(navigationProperty, "navigationProperty"); MetadataProviderEdmEntitySet targetEntitySet = null; if (this.navigationTargetMapping == null || !this.navigationTargetMapping.TryGetValue(navigationProperty, out targetEntitySet)) { string declaringTypeName = navigationProperty.DeclaringEntityType().FullName(); ResourceType declaringResourceType = this.model.MetadataProvider.TryResolveResourceType(declaringTypeName); ResourceProperty navigationResourceProperty = declaringResourceType.TryResolvePropertiesDeclaredOnThisTypeByName(navigationProperty.Name); if (navigationResourceProperty != null && navigationResourceProperty.TypeKind == ResourceTypeKind.EntityType) { // Calling this method causes the model to load all the metadata for the given association. // Hence we do not need to add this to the target mapping explicitly this.model.PairUpNavigationProperty(this.resourceSet, declaringResourceType, navigationResourceProperty); // Since the entity set or target entity set might be hidden, the navigation target might not get added // from the previous call if (this.navigationTargetMapping != null) { this.navigationTargetMapping.TryGetValue(navigationProperty, out targetEntitySet); } } } if (this.model.Mode == MetadataProviderEdmModelMode.SelectAndExpandParsing) { // When parsing $select/$expand, the URI parser has no way of knowing which sets are visible. So, if a navigation property is found // that does not have a target entity set, then it means that the target set is hidden. To avoid disclosing the existence of the set, act // as if the property does not exist. if (targetEntitySet == null) { // We're playing a dangerous game here. We are trying to throw the SAME ERROR MESSAGE that the Uri Parser would throw // for some property that is not found. If it looks any different, a client could detect the difference and learn about // the existance of a navigation property that should have been hidden. throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidPropertyNameSpecified(navigationProperty.Name, navigationProperty.DeclaringEntityType().FullName())); } } if (this.model.Mode == MetadataProviderEdmModelMode.UriPathParsing) { // As with select and expand, when parsing the path, the URI parser has no way of knowing which sets are visible. See above. if (targetEntitySet == null) { // Same fragility as above. throw DataServiceException.CreateResourceNotFound(navigationProperty.Name); } } return(targetEntitySet); }
/// <summary> /// Validates if a service action is advertisable. /// </summary> /// <param name="resourceType">Resource type to which the service action is bound to.</param> /// <param name="serviceAction">Service action to be validated for advertisement.</param> /// <param name="existingOperations">The current set of actions. Used to avoid duplicate actions.</param> /// <returns>Validated service operation to be advertised. Null, if the service operation is not suppose to be advertised.</returns> private OperationWrapper ValidateCanAdvertiseServiceAction(ResourceType resourceType, ServiceAction serviceAction, OperationCache existingOperations) { Debug.Assert(resourceType != null && resourceType.ResourceTypeKind == ResourceTypeKind.EntityType, "resourceType != null && resourceType.ResourceTypeKind == ResourceTypeKind.EntityType"); if (serviceAction == null) { return(null); } Debug.Assert(!String.IsNullOrEmpty(serviceAction.Name), "The name of the service operation was null or empty"); if (existingOperations.Contains(serviceAction)) { throw new DataServiceException(500, Strings.DataServiceActionProviderWrapper_DuplicateAction(serviceAction.Name)); } ServiceActionParameter bindingParameter = (ServiceActionParameter)serviceAction.BindingParameter; if (bindingParameter == null) { Debug.Assert(!String.IsNullOrEmpty(serviceAction.Name), "The name of the service action was null or empty"); throw new DataServiceException(500, Strings.DataServiceActionProviderWrapper_ServiceActionBindingParameterNull(serviceAction.Name)); } ResourceType bindingParameterType = bindingParameter.ParameterType; Debug.Assert(bindingParameterType != null, "bindingParameterType != null"); // We only support advertising actions for entities and not entity collections. Since resourceType must be an entity type, // IsAssignableFrom will fail when the bindingParameterType is an entity collection type. if (!bindingParameterType.IsAssignableFrom(resourceType)) { throw new DataServiceException(500, Strings.DataServiceActionProviderWrapper_ResourceTypeMustBeAssignableToBindingParameterResourceType(serviceAction.Name, bindingParameterType.FullName, resourceType.FullName)); } Debug.Assert(bindingParameterType.ResourceTypeKind == ResourceTypeKind.EntityType, "We only support advertising actions for entities and not entity collections."); OperationWrapper operationWrapper = this.provider.ValidateOperation(serviceAction); if (operationWrapper != null) { existingOperations.Add(operationWrapper); } return(operationWrapper); }
/// <summary>Generates a not of expression.</summary> /// <param name="expr">Input expression.</param> /// <returns>The generated expression.</returns> internal static Expression GenerateNot(Expression expr) { if (OpenTypeMethods.IsOpenPropertyExpression(expr)) { return(OpenTypeMethods.NotExpression(expr)); } if (expr.Type == typeof(bool) || expr.Type == typeof(Nullable <bool>)) { // Expression.Not will take numerics and apply '~' to them, thus the extra check here. return(Expression.Not(expr)); } else { string message = Strings.RequestQueryParser_NotDoesNotSupportType(expr.Type); throw DataServiceException.CreateSyntaxError(message); } }
/// <summary> /// 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. /// </summary> /// <param name="left">Left hand side expression</param> /// <param name="right">Right hand side expression</param> /// <param name="operatorKind">gt or lt operator token</param> /// <returns>Resulting comparison expression (has a Boolean value)</returns> private Expression GenerateNullAwareComparison(Expression left, Expression right, BinaryOperatorKind operatorKind) { Debug.Assert( operatorKind == BinaryOperatorKind.GreaterThan || operatorKind == BinaryOperatorKind.LessThan, "Only GreaterThan or LessThan operators are supported by the GenerateNullAwateComparison method for now."); 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, operatorKind)); } switch (operatorKind) { case BinaryOperatorKind.GreaterThan: // (left != null) && ((right == null) || Compare(left, right) > 0) if (left == ExpressionUtils.NullLiteral) { return(Expression.Constant(false, typeof(bool))); } else if (right == ExpressionUtils.NullLiteral) { return(ExpressionGenerator.GenerateNotEqual(left, Expression.Constant(null, left.Type))); } else { return(ExpressionGenerator.GenerateLogicalAnd( ExpressionGenerator.GenerateNotEqual(left, Expression.Constant(null, left.Type)), ExpressionGenerator.GenerateLogicalOr( ExpressionGenerator.GenerateEqual(right, Expression.Constant(null, right.Type)), this.GenerateComparisonExpression(left, right, operatorKind)))); } case BinaryOperatorKind.LessThan: // (right != null) && ((left == null) || Compare(left, right) < 0) if (right == ExpressionUtils.NullLiteral) { return(Expression.Constant(false, typeof(bool))); } else if (left == ExpressionUtils.NullLiteral) { return(ExpressionGenerator.GenerateNotEqual(right, Expression.Constant(null, right.Type))); } else { return(ExpressionGenerator.GenerateLogicalAnd( ExpressionGenerator.GenerateNotEqual(right, Expression.Constant(null, left.Type)), ExpressionGenerator.GenerateLogicalOr( ExpressionGenerator.GenerateEqual(left, Expression.Constant(null, right.Type)), this.GenerateComparisonExpression(left, right, operatorKind)))); } default: // For now only < and > are supported as we use this only from $skiptoken string message = ServiceStrings.RequestQueryParser_NullOperatorUnsupported(operatorKind); throw DataServiceException.CreateSyntaxError(message); } }