/// <summary>Creates new root node for the projection tree.</summary> /// <param name="resourceSetWrapper">The resource set of the root level of the query.</param> /// <param name="orderingInfo">The ordering info for this node. null means no ordering to be applied.</param> /// <param name="filter">The filter for this node. null means no filter to be applied.</param> /// <param name="skipCount">Number of results to skip. null means no results to be skipped.</param> /// <param name="takeCount">Maximum number of results to return. null means return all available results.</param> /// <param name="maxResultsExpected">Maximum number of expected results. Hint that the provider should return /// at least maxResultsExpected + 1 results (if available).</param> /// <param name="expandPaths">The list of expanded paths.</param> /// <param name="baseResourceType">The resource type for all entities in this query.</param> /// <param name="selectExpandClause">The select expand clause for the current node from the URI Parser.</param> internal RootProjectionNode( ResourceSetWrapper resourceSetWrapper, OrderingInfo orderingInfo, Expression filter, int? skipCount, int? takeCount, int? maxResultsExpected, List<ExpandSegmentCollection> expandPaths, ResourceType baseResourceType, SelectExpandClause selectExpandClause) : base( String.Empty, null, null, resourceSetWrapper, orderingInfo, filter, skipCount, takeCount, maxResultsExpected, selectExpandClause) { Debug.Assert(baseResourceType != null, "baseResourceType != null"); this.expandPaths = expandPaths; this.baseResourceType = baseResourceType; }
/// <summary>Creates new root node for the projection tree.</summary> /// <param name="resourceSetWrapper">The resource set of the root level of the query.</param> /// <param name="orderingInfo">The ordering info for this node. null means no ordering to be applied.</param> /// <param name="filter">The filter for this node. null means no filter to be applied.</param> /// <param name="skipCount">Number of results to skip. null means no results to be skipped.</param> /// <param name="takeCount">Maximum number of results to return. null means return all available results.</param> /// <param name="maxResultsExpected">Maximum number of expected results. Hint that the provider should return /// at least maxResultsExpected + 1 results (if available).</param> /// <param name="expandPaths">The list of expanded paths.</param> /// <param name="baseResourceType">The resource type for all entities in this query.</param> /// <param name="selectExpandClause">The select expand clause for the current node from the URI Parser.</param> internal RootProjectionNode( ResourceSetWrapper resourceSetWrapper, OrderingInfo orderingInfo, Expression filter, int?skipCount, int?takeCount, int?maxResultsExpected, List <ExpandSegmentCollection> expandPaths, ResourceType baseResourceType, SelectExpandClause selectExpandClause) : base( String.Empty, null, null, resourceSetWrapper, orderingInfo, filter, skipCount, takeCount, maxResultsExpected, selectExpandClause) { Debug.Assert(baseResourceType != null, "baseResourceType != null"); this.expandPaths = expandPaths; this.baseResourceType = baseResourceType; }
/// <summary>Creates new instance of node representing expanded navigation property.</summary> /// <param name="propertyName">The name of the property to project and expand.</param> /// <param name="property">The <see cref="ResourceProperty"/> for this property. Can only be null for the root node.</param> /// <param name="targetResourceType">Target resource type on which the expansion needs to happen.</param> /// <param name="resourceSetWrapper">The resource set to which the expansion leads.</param> /// <param name="orderingInfo">The ordering info for this node. null means no ordering to be applied.</param> /// <param name="filter">The filter for this node. null means no filter to be applied.</param> /// <param name="skipCount">Number of results to skip. null means no results to be skipped.</param> /// <param name="takeCount">Maximum number of results to return. null means return all available results.</param> /// <param name="maxResultsExpected">Maximum number of expected results. Hint that the provider should return /// at least maxResultsExpected + 1 results (if available).</param> /// <param name="selectExpandClause">The select expand clause for the current node from the URI Parser.</param> internal ExpandedProjectionNode( string propertyName, ResourceProperty property, ResourceType targetResourceType, ResourceSetWrapper resourceSetWrapper, OrderingInfo orderingInfo, Expression filter, int?skipCount, int?takeCount, int?maxResultsExpected, SelectExpandClause selectExpandClause) : base(propertyName, property, targetResourceType) { Debug.Assert(resourceSetWrapper != null, "resourceSetWrapper != null"); Debug.Assert(property != null || (propertyName.Length == 0 && targetResourceType == null), "We don't support open navigation properties."); Debug.Assert(property == null || targetResourceType.TryResolvePropertyName(property.Name) != null, "Property must exist on the target resource type"); Debug.Assert(selectExpandClause != null, "selectExpandClause != null"); this.resourceSetWrapper = resourceSetWrapper; this.orderingInfo = orderingInfo; this.filter = filter; this.skipCount = skipCount; this.takeCount = takeCount; this.maxResultsExpected = maxResultsExpected; this.nodes = new List <ProjectionNode>(); this.hasExpandedPropertyOnDerivedType = false; this.selectExpandClause = selectExpandClause; }
/// <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> /// Finds out if the given <paramref name="orderingInfo"/> required a skip token /// expression in the expansion /// </summary> /// <param name="orderingInfo">Input orderingInfo.</param> /// <returns>true if skip token expression is needed, false otherwise</returns> internal static bool IsSkipTokenRequired(OrderingInfo orderingInfo) { if (orderingInfo != null && orderingInfo.IsPaged) { foreach (OrderingExpression o in orderingInfo.OrderingExpressions) { LambdaExpression l = (LambdaExpression)o.Expression; NeedSkipTokenVisitor visitor = new NeedSkipTokenVisitor(); visitor.Visit(l.Body); if (visitor.NeedSkipToken) { return true; } } } return false; }
/// <summary>Makes the expression that is used as a filter corresponding to skip token.</summary> /// <param name="topLevelOrderingInfo">Ordering expression.</param> /// <param name="skipToken">The provided skip token.</param> /// <param name="parameterType">The parameter type of the lambda.</param> /// <returns>LambdaExpression corresponding to the skip token filter.</returns> internal LambdaExpression BuildSkipTokenFilter(OrderingInfo topLevelOrderingInfo, IList<object> skipToken, Type parameterType) { ParameterExpression element = Expression.Parameter(parameterType, "element"); Expression lastCondition = Expression.Constant(true, typeof(bool)); Expression lastMatch = Expression.Constant(false, typeof(bool)); foreach (var v in WebUtil.Zip(topLevelOrderingInfo.OrderingExpressions, skipToken, (x, y) => new { Order = x, Value = y })) { BinaryOperatorKind comparisonExp = v.Order.IsAscending ? BinaryOperatorKind.GreaterThan : BinaryOperatorKind.LessThan; Expression fixedLambda = ParameterReplacerVisitor.Replace( ((LambdaExpression)v.Order.Expression).Body, ((LambdaExpression)v.Order.Expression).Parameters[0], element); // TODO: this will be an EnumNode if $skiptoken contains enum constant. ConstantNode node; var lexer = new ExpressionLexer((string)v.Value); bool success = TokenToQueryNodeTranslator.TryCreateLiteral(lexer.CurrentToken, out node); Debug.Assert(success, "Was not a literal"); node = this.EnsureCorrectTypeAndPrecisionForLFDM(node, fixedLambda.Type); Expression right = this.nodeToExpressionTranslator.TranslateNode(node); Expression comparison = ExpressionGenerator.GenerateLogicalAnd( lastCondition, this.GenerateNullAwareComparison(fixedLambda, right, comparisonExp)); lastMatch = ExpressionGenerator.GenerateLogicalOr(lastMatch, comparison); lastCondition = ExpressionGenerator.GenerateLogicalAnd( lastCondition, this.GenerateComparisonExpression(fixedLambda, right, BinaryOperatorKind.Equal)); } lastMatch = ExpressionUtils.EnsurePredicateExpressionIsBoolean(lastMatch); Debug.Assert(lastMatch.Type == typeof(bool), "Skip token should generate boolean expression."); return Expression.Lambda(lastMatch, element); }
/// <summary> /// Obtains a collection of resource properties that are needed for skip token generation /// </summary> /// <param name="orderingInfo">Input orderingInfo.</param> /// <param name="rt">Resource type for which to collect the skip token properties</param> /// <returns>Collection of resource properties used in $skiptoken</returns> internal static ICollection<ResourceProperty> CollectSkipTokenProperties(OrderingInfo orderingInfo, ResourceType rt) { Debug.Assert(orderingInfo != null, "Must have valid ordering information to collect skip token properties"); Debug.Assert(orderingInfo.IsPaged, "Must have paging enabled to collection skip token properties"); List<ResourceProperty> resourceProperties = new List<ResourceProperty>(); foreach (OrderingExpression o in orderingInfo.OrderingExpressions) { LambdaExpression l = (LambdaExpression)o.Expression; NeedSkipTokenVisitor visitor = new NeedSkipTokenVisitor(rt); visitor.Visit(l.Body); if (visitor.NeedSkipToken) { return null; } Debug.Assert(visitor.Property != null, "Must have a valid property if skip token is not needed"); resourceProperties.Add(visitor.Property); } return resourceProperties; }
/// <summary>Initializes a new <see cref="ExpandSegment"/> instance.</summary> /// <param name="name">Segment name.</param> /// <param name="filter">Filter expression for segment, possibly null.</param> /// <param name="maxResultsExpected"> /// Expand providers may choose to return at most MaxResultsExpected + 1 elements to allow the /// data service to detect a failure to meet this constraint. /// </param> /// <param name="container">Container to which the segment belongs; possibly null.</param> /// <param name="targetResourceType">Target resource type on which the expansion needs to happen.</param> /// <param name="expandedProperty">Property expanded by this expand segment</param> /// <param name="orderingInfo">Collection of ordering information for this segment, used for paging</param> internal ExpandSegment( string name, Expression filter, int maxResultsExpected, ResourceSetWrapper container, ResourceType targetResourceType, ResourceProperty expandedProperty, OrderingInfo orderingInfo) { WebUtil.CheckArgumentNull(name, "name"); CheckFilterType(filter); this.name = name; this.filter = filter; this.container = container; this.maxResultsExpected = maxResultsExpected; this.expandedProperty = expandedProperty; this.orderingInfo = orderingInfo; this.targetResourceType = targetResourceType; }
/// <summary>Creates new instance of node representing expanded navigation property.</summary> /// <param name="propertyName">The name of the property to project and expand.</param> /// <param name="property">The <see cref="ResourceProperty"/> for this property. Can only be null for the root node.</param> /// <param name="targetResourceType">Target resource type on which the expansion needs to happen.</param> /// <param name="resourceSetWrapper">The resource set to which the expansion leads.</param> /// <param name="orderingInfo">The ordering info for this node. null means no ordering to be applied.</param> /// <param name="filter">The filter for this node. null means no filter to be applied.</param> /// <param name="skipCount">Number of results to skip. null means no results to be skipped.</param> /// <param name="takeCount">Maximum number of results to return. null means return all available results.</param> /// <param name="maxResultsExpected">Maximum number of expected results. Hint that the provider should return /// at least maxResultsExpected + 1 results (if available).</param> /// <param name="selectExpandClause">The select expand clause for the current node from the URI Parser.</param> internal ExpandedProjectionNode( string propertyName, ResourceProperty property, ResourceType targetResourceType, ResourceSetWrapper resourceSetWrapper, OrderingInfo orderingInfo, Expression filter, int? skipCount, int? takeCount, int? maxResultsExpected, SelectExpandClause selectExpandClause) : base(propertyName, property, targetResourceType) { Debug.Assert(resourceSetWrapper != null, "resourceSetWrapper != null"); Debug.Assert(property != null || (propertyName.Length == 0 && targetResourceType == null), "We don't support open navigation properties."); Debug.Assert(property == null || targetResourceType.TryResolvePropertyName(property.Name) != null, "Property must exist on the target resource type"); Debug.Assert(selectExpandClause != null, "selectExpandClause != null"); this.resourceSetWrapper = resourceSetWrapper; this.orderingInfo = orderingInfo; this.filter = filter; this.skipCount = skipCount; this.takeCount = takeCount; this.maxResultsExpected = maxResultsExpected; this.nodes = new List<ProjectionNode>(); this.hasExpandedPropertyOnDerivedType = false; this.selectExpandClause = selectExpandClause; }
/// <summary> /// Builds the collection of ordering expressions including implicit ordering if paging is required at top level /// </summary> private void ObtainOrderingExpressions() { const String Comma = ","; const char Space = ' '; const String AscendingOrderIdentifier = "asc"; const string QuestionMark = "?"; const string EqualMark = "="; const string AndMark = "&"; if (!string.IsNullOrEmpty(this.service.OperationContext.RequestMessage.GetQueryStringItem(XmlConstants.HttpQueryStringOrderBy))) { this.CheckSetQueryApplicable(); } Debug.Assert(this.topLevelOrderingInfo == null, "Must only be called once per query"); ResourceType rt = this.description.TargetResourceType; this.topLevelOrderingInfo = new OrderingInfo(this.IsStandardPaged); OrderByClause orderByClause = new RequestExpressionParser(this.service, this.description).ParseOrderBy(); // We need to generate ordering expression(when we don't have top level $orderby), if either the result is paged, or we have // skip or top count request because in that case, the skip or top has to happen in // the expand provider. if (orderByClause == null) { if (this.IsStandardPaged || this.topCount.HasValue || this.skipCount.HasValue) { Uri requestUri = this.service.OperationContext.AbsoluteRequestUri; StringBuilder orderBy = string.IsNullOrEmpty(requestUri.Query) ? new StringBuilder(requestUri.AbsoluteUri + QuestionMark) : new StringBuilder(requestUri.AbsoluteUri + AndMark); orderBy.Append(XmlConstants.HttpQueryStringOrderBy + EqualMark); String separator = String.Empty; foreach (var keyProp in this.description.TargetResourceSet.GetKeyPropertiesForOrderBy()) { orderBy.Append(separator).Append(keyProp.Name).Append(Space).Append(AscendingOrderIdentifier); separator = Comma; } var uriParser = RequestUriProcessor.CreateUriParserWithBatchReferenceCallback(this.service, new Uri(orderBy.ToString())); orderByClause = uriParser.ParseOrderBy(); } else { return; } } ParameterExpression elementParameter = Expression.Parameter(rt.InstanceType, "element"); var translator = NodeToExpressionTranslator.Create(this.service, this.description, elementParameter); IEnumerable<OrderingExpression> ordering = translator.TranslateOrderBy(orderByClause); this.topLevelOrderingInfo.AddRange(ordering); if (this.IsStandardPaged) { this.description.SkipTokenExpressionCount = this.topLevelOrderingInfo.OrderingExpressions.Count; this.description.SkipTokenProperties = NeedSkipTokenVisitor.CollectSkipTokenProperties(this.topLevelOrderingInfo, rt); } }
/// <summary>Initializes a new <see cref="RequestQueryProcessor"/> instance.</summary> /// <param name="service">Service with data and configuration.</param> /// <param name="description">Description for request processed so far.</param> private RequestQueryProcessor(IDataService service, RequestDescription description) { this.service = service; this.description = description; #if DEBUG this.orderApplied = false; #endif this.skipCount = null; this.topCount = null; this.queryExpression = description.RequestExpression; this.setQueryApplicable = (description.TargetKind == RequestTargetKind.Resource && !description.IsSingleResult) || description.CountOption == RequestQueryCountOption.CountSegment; // Server Driven Paging is not considered for the following cases: // 1. Top level result is not or resource type or it is a single valued result. // 2. $count segment provided. // 3. Non-GET requests do not honor SDP. // 4. Only exception for Non-GET requests is if the request is coming from a Service // operation that returns a set of result values of entity type. this.pagingApplicable = (description.TargetKind == RequestTargetKind.Resource && !description.IsSingleResult) && (description.CountOption != RequestQueryCountOption.CountSegment) && !description.IsRequestForEnumServiceOperation && (service.OperationContext.RequestMessage.HttpVerb.IsQuery() || description.SegmentInfos[0].TargetSource == RequestTargetSource.ServiceOperation); this.appliedCustomPaging = false; this.rootProjectionNode = null; this.topLevelOrderingInfo = null; this.skipTokenExpressionBuilder = new SkipTokenExpressionBuilder(NodeToExpressionTranslator.Create(this.service, description, Expression.Parameter(typeof(object)))); }
/// <summary> /// Creates an instance of <see cref="ExpandSegment"/> based on the metadata in the given <see cref="ExpandedNavigationSelectItem"/>. /// </summary> /// <param name="expandItem">The metadata-bound expand segment to create the <see cref="ExpandSegment"/> from.</param> /// <param name="currentResourceType">The current resource type.</param> /// <returns>The created <see cref="ExpandSegment"/>.</returns> private ExpandSegment CreateExpandSegment(ExpandedNavigationSelectItem expandItem, ResourceType currentResourceType) { Debug.Assert(expandItem != null, "expandItem != null"); Debug.Assert(currentResourceType != null, "currentResourceType != null"); // pull the Resource* instances from annotations on the IEdm* instances. IEdmNavigationProperty navigationProperty = ((NavigationPropertySegment)expandItem.PathToNavigationProperty.LastSegment).NavigationProperty; ResourceProperty property = ((IResourcePropertyBasedEdmProperty)navigationProperty).ResourceProperty; Debug.Assert(property != null, "property != null"); ResourceSetWrapper targetResourceSet = ((IResourceSetBasedEdmEntitySet)expandItem.NavigationSource).ResourceSet; Debug.Assert(targetResourceSet != null, "targetResourceSet != null"); // Further scope the type based on type segments in the path. currentResourceType = this.GetTargetResourceTypeFromTypeSegments(expandItem.PathToNavigationProperty, currentResourceType); Debug.Assert(currentResourceType != null, "currentResourceType != null"); // The expanded resource type may require higher response version. Update version of the response accordingly // // Note the response DSV is payload specific and since for GET we won't know what DSV the instances to be // serialized will require until serialization time which happens after the headers are written, // the best we can do is to determine this at the set level. this.description.UpdateVersion(targetResourceSet, this.service); bool singleResult = property.Kind == ResourcePropertyKind.ResourceReference; DataServiceConfiguration.CheckResourceRightsForRead(targetResourceSet, singleResult); Expression filter = DataServiceConfiguration.ComposeQueryInterceptors(this.service.Instance, targetResourceSet); if (targetResourceSet.PageSize != 0 && !singleResult && !this.IsCustomPaged) { OrderingInfo internalOrderingInfo = new OrderingInfo(true); ParameterExpression p = Expression.Parameter(targetResourceSet.ResourceType.InstanceType, "p"); foreach (var keyProp in targetResourceSet.GetKeyPropertiesForOrderBy()) { Expression e; if (keyProp.CanReflectOnInstanceTypeProperty) { e = Expression.Property(p, targetResourceSet.ResourceType.GetPropertyInfo(keyProp)); } else { // object LateBoundMethods.GetValue(object, ResourceProperty) e = Expression.Call( null, /*instance*/ DataServiceProviderMethods.GetValueMethodInfo, p, Expression.Constant(keyProp)); e = Expression.Convert(e, keyProp.Type); } internalOrderingInfo.Add(new OrderingExpression(Expression.Lambda(e, p), true)); } return new ExpandSegment(property.Name, filter, targetResourceSet.PageSize, targetResourceSet, currentResourceType, property, internalOrderingInfo); } if (!singleResult && this.IsCustomPaged) { // Expansion of collection could result in custom paging provider giving next link, so we need to set the null continuation token. this.CheckAndApplyCustomPaging(null); } return new ExpandSegment(property.Name, filter, this.service.Configuration.MaxResultsPerCollection, targetResourceSet, currentResourceType, property, null); }
/// <summary> /// Updates the topLevelOrderingInfo member with the new collection of expressions that /// dereference the ExpandedElement property on the top level wrapper object /// </summary> /// <param name="resultWrapperType">Type of top level wrapper object</param> private void UpdateOrderingInfoWithSkipTokenWrapper(Type resultWrapperType) { OrderingInfo newOrderingInfo = new OrderingInfo(true); ParameterExpression wrapperParameter = Expression.Parameter(resultWrapperType, "w"); foreach (var ordering in this.topLevelOrderingInfo.OrderingExpressions) { LambdaExpression oldExpression = (LambdaExpression)ordering.Expression; Expression newOrdering = ParameterReplacerVisitor.Replace( oldExpression.Body, oldExpression.Parameters[0], Expression.MakeMemberAccess(wrapperParameter, resultWrapperType.GetProperty("ExpandedElement"))); newOrderingInfo.Add(new OrderingExpression(Expression.Lambda(newOrdering, wrapperParameter), ordering.IsAscending)); } this.topLevelOrderingInfo = newOrderingInfo; }