Exemplo n.º 1
0
        /// <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;
        }
Exemplo n.º 2
0
        /// <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;
        }
Exemplo n.º 3
0
        /// <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;
        }
Exemplo n.º 4
0
        /// <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;
        }
Exemplo n.º 5
0
        /// <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);
        }
Exemplo n.º 7
0
        /// <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;
        }
Exemplo n.º 8
0
 /// <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;
 }
Exemplo n.º 9
0
        /// <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;
        }
Exemplo n.º 10
0
        /// <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);
            }
        }
Exemplo n.º 11
0
        /// <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))));
        }
Exemplo n.º 12
0
        /// <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);
        }
Exemplo n.º 13
0
        /// <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;
        }