/// <summary> /// Normalize a <see cref="SelectToken"/>. /// </summary> /// <param name="selectToken">The select token to normalize</param> /// <returns>Normalized SelectToken</returns> public static SelectToken NormalizeSelectTree(SelectToken selectToken) { // Be noted: It's not allowed have multple select clause with same path. // For example: $select=abc($top=2),abc($skip=2) is not allowed by design. // Cusotmer should combine them together, for example: $select=abc($top=2;$skip=2). // The logic is different with ExpandTreeNormalizer. We should change the logic in ExpandTreeNormalizer // in next breaking change version. VerifySelectToken(selectToken); // To normalize the select tree we need to: // invert the path tree on each of its select term tokens selectToken = NormalizeSelectPaths(selectToken); return(selectToken); }
/// <summary> /// add a new expandTermToken into an exisiting token, adding any additional levels and trees along the way. /// </summary> /// <param name="existingToken">the exisiting (already expanded) token</param> /// <param name="newToken">the new (already expanded) token</param> /// <returns>the combined token, or, if the two are mutually exclusive, the same tokens</returns> public ExpandTermToken CombineTerms(ExpandTermToken existingToken, ExpandTermToken newToken) { Debug.Assert(new PathSegmentTokenEqualityComparer().Equals(existingToken.PathToNavigationProp, newToken.PathToNavigationProp), "Paths should be equal."); List <ExpandTermToken> childNodes = CombineChildNodes(existingToken, newToken).ToList(); SelectToken combinedSelects = CombineSelects(existingToken, newToken); return(new ExpandTermToken( existingToken.PathToNavigationProp, existingToken.FilterOption, existingToken.OrderByOptions, existingToken.TopOption, existingToken.SkipOption, existingToken.CountQueryOption, existingToken.LevelsOption, existingToken.SearchOption, combinedSelects, new ExpandToken(childNodes))); }
private static SelectToken NormalizeSelectPaths(SelectToken selectToken) { if (selectToken != null) { // iterate through each select term token, and reverse the tree in its path property foreach (SelectTermToken term in selectToken.SelectTerms) { term.PathToProperty = term.PathToProperty.Reverse(); // we also need to call the select token normalizer for this level to reverse the select paths if (term.SelectOption != null) { term.SelectOption = NormalizeSelectPaths(term.SelectOption); } } } return(selectToken); }
/// <summary> /// Bind the select and expand clause <see cref="SelectExpandClause"/> at this level. /// </summary> /// <param name="expandToken">The expand token to visit.</param> /// <param name="selectToken">The select token to visit.</param> /// <param name="segments">The parsed segments to visit.</param> /// <returns>The null or the built select and expand clause.</returns> private SelectExpandClause BindSelectExpand(ExpandToken expandToken, SelectToken selectToken, IList <ODataPathSegment> segments, IEdmNavigationSource navigationSource, IEdmTypeReference elementType, HashSet <EndPathToken> generatedProperties = null, bool collapsed = false) { if (expandToken != null || selectToken != null) { BindingState binding = CreateBindingState(this.Configuration, navigationSource, elementType, generatedProperties, collapsed); SelectExpandBinder selectExpandBinder = new SelectExpandBinder(this.Configuration, new ODataPathInfo(new ODataPath(segments)), binding); return(selectExpandBinder.Bind(expandToken, selectToken)); } else { // It's better to return null for both Expand and Select are null. // However, in order to be consistent, we returns the empty SelectExpandClause with AllSelected = true. return(new SelectExpandClause(new Collection <SelectItem>(), true)); } }
/// <summary> /// Add semantic meaning to a Select or Expand Token /// </summary> /// <param name="odataPathInfo">The path info from Uri path.</param> /// <param name="expandToken">the syntactically parsed expand token</param> /// <param name="selectToken">the syntactically parsed select token</param> /// <param name="configuration">The configuration to use for parsing.</param> /// <param name="state">The state of binding.</param> /// <returns>A select expand clause bound to metadata.</returns> public static SelectExpandClause Bind( ODataPathInfo odataPathInfo, ExpandToken expandToken, SelectToken selectToken, ODataUriParserConfiguration configuration, BindingState state) { ExpandToken normalizedExpand = ExpandTreeNormalizer.NormalizeExpandTree(expandToken); SelectToken normalizedSelect = SelectTreeNormalizer.NormalizeSelectTree(selectToken); SelectExpandBinder selectExpandBinder = new SelectExpandBinder(configuration, odataPathInfo, state); SelectExpandClause clause = selectExpandBinder.Bind(normalizedExpand, normalizedSelect); SelectExpandClauseFinisher.AddExplicitNavPropLinksWhereNecessary(clause); new ExpandDepthAndCountValidator(configuration.Settings.MaximumExpansionDepth, configuration.Settings.MaximumExpansionCount).Validate(clause); return(clause); }
/// <summary> /// Add semantic meaning to a Select or Expand Token /// </summary> /// <param name="odataPathInfo">The path info from Uri path.</param> /// <param name="expandToken">the syntactically parsed expand token</param> /// <param name="selectToken">the syntactically parsed select token</param> /// <param name="configuration">The configuration to use for parsing.</param> /// <returns>A select expand clause bound to metadata.</returns> public SelectExpandClause Bind( ODataPathInfo odataPathInfo, ExpandToken expandToken, SelectToken selectToken, ODataUriParserConfiguration configuration) { ExpandToken unifiedSelectExpandToken = SelectExpandSyntacticUnifier.Combine(expandToken, selectToken); ExpandTreeNormalizer expandTreeNormalizer = new ExpandTreeNormalizer(); ExpandToken normalizedSelectExpandToken = expandTreeNormalizer.NormalizeExpandTree(unifiedSelectExpandToken); SelectExpandBinder selectExpandBinder = new SelectExpandBinder(configuration, odataPathInfo); SelectExpandClause clause = selectExpandBinder.Bind(normalizedSelectExpandToken); SelectExpandClauseFinisher.AddExplicitNavPropLinksWhereNecessary(clause); new ExpandDepthAndCountValidator(configuration.Settings.MaximumExpansionDepth, configuration.Settings.MaximumExpansionCount).Validate(clause); return(clause); }
/// <summary> /// Visits the top level select token /// </summary> /// <param name="tokenIn">the select token to visit</param> /// <returns>A new SelectExpandClause decorated with the information from the selectToken</returns> public SelectExpandClause Bind(SelectToken tokenIn) { if (tokenIn == null || !tokenIn.Properties.Any()) { // if there are no properties selected for this level, then by default we select // all properties (including nav prop links, functions, actions, and structural properties) this.visitor.DecoratedExpandClause.SetAllSelected(true); } else { // if there are properties selected for this level, then we return only // those specific properties in the payload, so clear the all selected flag // for this level. this.visitor.DecoratedExpandClause.SetAllSelected(false); foreach (PathSegmentToken property in tokenIn.Properties) { property.Accept(this.visitor); } } return(this.visitor.DecoratedExpandClause); }
/// <summary> /// Building of a PathSegmentToken, continue parsing any expand options (nested $filter, $expand, etc) /// to build up an ExpandTermToken which fully represents the tree that makes up this expand term. /// </summary> /// <param name="pathToken">The PathSegmentToken representing the parsed expand path whose options we are now parsing.</param> /// <param name="optionsText">A string of the text between the parenthesis after an expand option.</param> /// <returns>The list of expand term tokens based on the path token, and all available expand options.</returns> internal List <ExpandTermToken> BuildExpandTermToken(PathSegmentToken pathToken, string optionsText) { // Setup a new lexer for parsing the optionsText this.lexer = new ExpressionLexer(optionsText ?? "", true /*moveToFirstToken*/, true /*useSemicolonDelimiter*/); // $expand option with star only support $ref option, $expand option property could be "*" or "*/$ref", special logic will be adopted. if (pathToken.Identifier == UriQueryConstants.Star || (pathToken.Identifier == UriQueryConstants.RefSegment && pathToken.NextToken.Identifier == UriQueryConstants.Star)) { return(BuildStarExpandTermToken(pathToken)); } QueryToken filterOption = null; IEnumerable <OrderByToken> orderByOptions = null; long? topOption = null; long? skipOption = null; bool? countOption = null; long? levelsOption = null; QueryToken searchOption = null; SelectToken selectOption = null; ExpandToken expandOption = null; ComputeToken computeOption = null; IEnumerable <QueryToken> applyOptions = null; if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen) { // advance past the '(' this.lexer.NextToken(); // Check for (), which is not allowed. if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.CloseParen) { throw new ODataException(ODataErrorStrings.UriParser_MissingExpandOption(pathToken.Identifier)); } // Look for all the supported query options while (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen) { string text = this.enableCaseInsensitiveBuiltinIdentifier ? this.lexer.CurrentToken.Text.ToLowerInvariant() : this.lexer.CurrentToken.Text; // Prepend '$' prefix if needed. if (this.enableNoDollarQueryOptions && !text.StartsWith(UriQueryConstants.DollarSign, StringComparison.Ordinal)) { text = string.Format(CultureInfo.InvariantCulture, "{0}{1}", UriQueryConstants.DollarSign, text); } switch (text) { case ExpressionConstants.QueryOptionFilter: // inner $filter filterOption = ParseInnerFilter(); break; case ExpressionConstants.QueryOptionOrderby: // inner $orderby orderByOptions = ParseInnerOrderBy(); break; case ExpressionConstants.QueryOptionTop: // inner $top topOption = ParseInnerTop(); break; case ExpressionConstants.QueryOptionSkip: // innner $skip skipOption = ParseInnerSkip(); break; case ExpressionConstants.QueryOptionCount: // inner $count countOption = ParseInnerCount(); break; case ExpressionConstants.QueryOptionSearch: // inner $search searchOption = ParseInnerSearch(); break; case ExpressionConstants.QueryOptionLevels: // inner $level levelsOption = ParseInnerLevel(); break; case ExpressionConstants.QueryOptionSelect: // inner $select selectOption = ParseInnerSelect(pathToken); break; case ExpressionConstants.QueryOptionExpand: // inner $expand expandOption = ParseInnerExpand(pathToken); break; case ExpressionConstants.QueryOptionCompute: // inner $compute computeOption = ParseInnerCompute(); break; case ExpressionConstants.QueryOptionApply: // inner $apply applyOptions = ParseInnerApply(); break; default: { throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); } } } // Move past the ')' this.lexer.NextToken(); } // Either there was no '(' at all or we just read past the ')' so we should be at the end if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End) { throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); } // TODO, there should be some check here in case pathToken identifier is $ref, select, expand and levels options are not allowed. List <ExpandTermToken> expandTermTokenList = new List <ExpandTermToken>(); ExpandTermToken currentToken = new ExpandTermToken(pathToken, filterOption, orderByOptions, topOption, skipOption, countOption, levelsOption, searchOption, selectOption, expandOption, computeOption, applyOptions); expandTermTokenList.Add(currentToken); return(expandTermTokenList); }
/// <summary> /// Building off a PathSegmentToken, continue parsing any select options (nested $filter, $expand, etc) /// to build up an SelectTermToken which fully represents the tree that makes up this select term. /// </summary> /// <param name="pathToken">The PathSegmentToken representing the parsed select path whose options we are now parsing.</param> /// <param name="optionsText">A string of the text between the parenthesis after a select option.</param> /// <returns>The select term token based on the path token, and all available select options.</returns> internal SelectTermToken BuildSelectTermToken(PathSegmentToken pathToken, string optionsText) { // Setup a new lexer for parsing the optionsText this.lexer = new ExpressionLexer(optionsText ?? "", true /*moveToFirstToken*/, true /*useSemicolonDelimiter*/); QueryToken filterOption = null; IEnumerable <OrderByToken> orderByOptions = null; long? topOption = null; long? skipOption = null; bool? countOption = null; QueryToken searchOption = null; SelectToken selectOption = null; ComputeToken computeOption = null; if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen) { // advance past the '(' this.lexer.NextToken(); // Check for (), which is not allowed. if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.CloseParen) { throw new ODataException(ODataErrorStrings.UriParser_MissingSelectOption(pathToken.Identifier)); } // Look for all the supported query options while (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen) { string text = this.enableCaseInsensitiveBuiltinIdentifier ? this.lexer.CurrentToken.Text.ToLowerInvariant() : this.lexer.CurrentToken.Text; // Prepend '$' prefix if needed. if (this.enableNoDollarQueryOptions && !text.StartsWith(UriQueryConstants.DollarSign, StringComparison.Ordinal)) { text = string.Format(CultureInfo.InvariantCulture, "{0}{1}", UriQueryConstants.DollarSign, text); } switch (text) { case ExpressionConstants.QueryOptionFilter: // inner $filter filterOption = ParseInnerFilter(); break; case ExpressionConstants.QueryOptionOrderby: // inner $orderby orderByOptions = ParseInnerOrderBy(); break; case ExpressionConstants.QueryOptionTop: // inner $top topOption = ParseInnerTop(); break; case ExpressionConstants.QueryOptionSkip: // innner $skip skipOption = ParseInnerSkip(); break; case ExpressionConstants.QueryOptionCount: // inner $count countOption = ParseInnerCount(); break; case ExpressionConstants.QueryOptionSearch: // inner $search searchOption = ParseInnerSearch(); break; case ExpressionConstants.QueryOptionSelect: // inner $select selectOption = ParseInnerSelect(pathToken); break; case ExpressionConstants.QueryOptionCompute: // inner $compute computeOption = ParseInnerCompute(); break; default: throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); } } // Move past the ')' this.lexer.NextToken(); } // Either there was no '(' at all or we just read past the ')' so we should be at the end if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End) { throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); } return(new SelectTermToken(pathToken, filterOption, orderByOptions, topOption, skipOption, countOption, searchOption, selectOption, computeOption)); }
/// <summary> /// Get rid of duplicate selected item in SelectToken /// </summary> /// <param name="selectToken">Select token to be dealt with</param> /// <returns>A new select term containing each of the </returns> private static SelectToken RemoveDuplicateSelect(SelectToken selectToken) { return(selectToken != null ? new SelectToken(selectToken.Properties.Distinct(new PathSegmentTokenEqualityComparer())) : null); }
/// <summary> /// Building off of a PathSegmentToken, continue parsing any expand options (nested $filter, $expand, etc) /// to build up an ExpandTermToken which fully represents the tree that makes up this expand term. /// </summary> /// <param name="pathToken">The PathSegmentToken representing the parsed expand path whose options we are now parsing.</param> /// <param name="optionsText">A string of the text between the parenthesis after an expand option.</param> /// <returns>The list of expand term tokens based on the path token, and all available expand options.</returns> internal List <ExpandTermToken> BuildExpandTermToken(PathSegmentToken pathToken, string optionsText) { // Setup a new lexer for parsing the optionsText this.lexer = new ExpressionLexer(optionsText ?? "", true /*moveToFirstToken*/, true /*useSemicolonDelimiter*/); // $expand option with star only support $ref option, $expand option property could be "*" or "*/$ref", special logic will be adopted. if (pathToken.Identifier == UriQueryConstants.Star || (pathToken.Identifier == UriQueryConstants.RefSegment && pathToken.NextToken.Identifier == UriQueryConstants.Star)) { return(BuildStarExpandTermToken(pathToken)); } QueryToken filterOption = null; IEnumerable <OrderByToken> orderByOptions = null; long? topOption = null; long? skipOption = null; bool? countOption = null; long? levelsOption = null; QueryToken searchOption = null; SelectToken selectOption = null; ExpandToken expandOption = null; if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen) { // advance past the '(' this.lexer.NextToken(); // Check for (), which is not allowed. if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.CloseParen) { throw new ODataException(ODataErrorStrings.UriParser_MissingExpandOption(pathToken.Identifier)); } // Look for all the supported query options while (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen) { string text = this.enableCaseInsensitiveBuiltinIdentifier ? this.lexer.CurrentToken.Text.ToLowerInvariant() : this.lexer.CurrentToken.Text; // Prepend '$' prefix if needed. if (this.enableNoDollarQueryOptions && !text.StartsWith(UriQueryConstants.DollarSign, StringComparison.Ordinal)) { text = string.Format(CultureInfo.InvariantCulture, "{0}{1}", UriQueryConstants.DollarSign, text); } switch (text) { case ExpressionConstants.QueryOptionFilter: { // advance to the equal sign this.lexer.NextToken(); string filterText = this.ReadQueryOption(); UriQueryExpressionParser filterParser = new UriQueryExpressionParser(this.MaxFilterDepth, enableCaseInsensitiveBuiltinIdentifier); filterOption = filterParser.ParseFilter(filterText); break; } case ExpressionConstants.QueryOptionOrderby: { // advance to the equal sign this.lexer.NextToken(); string orderByText = this.ReadQueryOption(); UriQueryExpressionParser orderbyParser = new UriQueryExpressionParser(this.MaxOrderByDepth, enableCaseInsensitiveBuiltinIdentifier); orderByOptions = orderbyParser.ParseOrderBy(orderByText); break; } case ExpressionConstants.QueryOptionTop: { // advance to the equal sign this.lexer.NextToken(); string topText = this.ReadQueryOption(); // TryParse requires a non-nullable non-negative long. long top; if (!long.TryParse(topText, out top) || top < 0) { throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidTopOption(topText)); } topOption = top; break; } case ExpressionConstants.QueryOptionSkip: { // advance to the equal sign this.lexer.NextToken(); string skipText = this.ReadQueryOption(); // TryParse requires a non-nullable non-negative long. long skip; if (!long.TryParse(skipText, out skip) || skip < 0) { throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidSkipOption(skipText)); } skipOption = skip; break; } case ExpressionConstants.QueryOptionCount: { // advance to the equal sign this.lexer.NextToken(); string countText = this.ReadQueryOption(); switch (countText) { case ExpressionConstants.KeywordTrue: { countOption = true; break; } case ExpressionConstants.KeywordFalse: { countOption = false; break; } default: { throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidCountOption(countText)); } } break; } case ExpressionConstants.QueryOptionLevels: { levelsOption = ResolveLevelOption(); break; } case ExpressionConstants.QueryOptionSearch: { // advance to the equal sign this.lexer.NextToken(); string searchText = this.ReadQueryOption(); SearchParser searchParser = new SearchParser(this.MaxSearchDepth); searchOption = searchParser.ParseSearch(searchText); break; } case ExpressionConstants.QueryOptionSelect: { // advance to the equal sign this.lexer.NextToken(); string selectText = this.ReadQueryOption(); SelectExpandParser innerSelectParser = new SelectExpandParser(selectText, this.maxRecursionDepth, enableCaseInsensitiveBuiltinIdentifier); selectOption = innerSelectParser.ParseSelect(); break; } case ExpressionConstants.QueryOptionExpand: { // advance to the equal sign this.lexer.NextToken(); string expandText = this.ReadQueryOption(); // As 2016/1/8, the navigation property is only supported in entity type, and will support in ComplexType in future. IEdmStructuredType targetEntityType = null; if (this.resolver != null && this.parentEntityType != null) { var parentProperty = this.resolver.ResolveProperty(parentEntityType, pathToken.Identifier) as IEdmNavigationProperty; // it is a navigation property, need to find the type. // Like $expand=Friends($expand=Trips($expand=*)), when expandText becomes "Trips($expand=*)", // find navigation property Trips of Friends, then get Entity type of Trips. if (parentProperty != null) { targetEntityType = parentProperty.ToEntityType(); } } SelectExpandParser innerExpandParser = new SelectExpandParser( resolver, expandText, targetEntityType, this.maxRecursionDepth - 1, this.enableCaseInsensitiveBuiltinIdentifier, this.enableNoDollarQueryOptions); expandOption = innerExpandParser.ParseExpand(); break; } default: { throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); } } } // Move past the ')' this.lexer.NextToken(); } // Either there was no '(' at all or we just read past the ')' so we should be at the end if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End) { throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); } // TODO, there should be some check here in case pathToken identifier is $ref, select, expand and levels options are not allowed. List <ExpandTermToken> expandTermTokenList = new List <ExpandTermToken>(); ExpandTermToken currentToken = new ExpandTermToken(pathToken, filterOption, orderByOptions, topOption, skipOption, countOption, levelsOption, searchOption, selectOption, expandOption); expandTermTokenList.Add(currentToken); return(expandTermTokenList); }
/// <summary> /// Visits a SelectToken /// </summary> /// <param name="tokenIn">The SelectToken to bind</param> /// <returns>A QueryNode bound to this SelectToken</returns> public virtual T Visit(SelectToken tokenIn) { throw new NotImplementedException(); }
/// <summary> /// Decorate an expand tree using a select token. /// </summary> /// <param name="subExpand">the already built sub expand</param> /// <param name="currentNavProp">the current navigation property</param> /// <param name="select">the select token to use</param> /// <returns>A new SelectExpand clause decorated with the select token.</returns> private SelectExpandClause DecorateExpandWithSelect(SelectExpandClause subExpand, IEdmNavigationProperty currentNavProp, SelectToken select) { SelectBinder selectBinder = new SelectBinder(this.Model, currentNavProp.ToEntityType(), this.Settings.SelectExpandLimit, subExpand, this.configuration.Resolver); return(selectBinder.Bind(select)); }
/// <summary> /// Parses the <paramref name="queryUri"/> and returns a new instance of <see cref="SyntacticTree"/> /// describing the query specified by the uri. /// </summary> /// <param name="queryUri">The absolute URI which holds the query to parse. This must be a path relative to the <paramref name="serviceBaseUri"/>.</param> /// <param name="serviceBaseUri">The base URI of the service.</param> /// <param name="maxDepth">The maximum depth of any single query part. Security setting to guard against DoS attacks causing stack overflows and such.</param> /// <returns>A new instance of <see cref="SyntacticTree"/> which represents the query specified in the <paramref name="queryUri"/>.</returns> public static SyntacticTree ParseUri(Uri queryUri, Uri serviceBaseUri, int maxDepth) { ExceptionUtils.CheckArgumentNotNull(queryUri, "queryUri"); if (!queryUri.IsAbsoluteUri) { throw new ArgumentException(Strings.SyntacticTree_UriMustBeAbsolute(queryUri), "queryUri"); } ExceptionUtils.CheckArgumentNotNull(serviceBaseUri, "serviceBaseUri"); if (!serviceBaseUri.IsAbsoluteUri) { throw new ArgumentException(Strings.SyntacticTree_UriMustBeAbsolute(serviceBaseUri), "serviceBaseUri"); } if (maxDepth <= 0) { throw new ArgumentException(Strings.SyntacticTree_MaxDepthInvalid, "maxDepth"); } UriPathParser pathParser = new UriPathParser(new ODataUriParserSettings() { PathLimit = maxDepth }); var path = pathParser.ParsePathIntoSegments(queryUri, serviceBaseUri); // COMPAT 32: Differencies in query options parsing in WCF DS // // We allow non-system $ query options in the lexical space. // We allow multiple instances of a custom or non-system $ query option in the lexical space. // TODO: we need to decide whether we want to allow multiple system $ query options with the same name (OIPI suggests that this is valid); we currently don't. List <CustomQueryOptionToken> queryOptions = QueryOptionUtils.ParseQueryOptions(queryUri); IDictionary <string, string> parameterAliases = queryOptions.GetParameterAliases(); QueryToken filter = null; string filterQuery = queryOptions.GetQueryOptionValueAndRemove(UriQueryConstants.FilterQueryOption); if (filterQuery != null) { UriQueryExpressionParser expressionParser = new UriQueryExpressionParser(maxDepth); filter = expressionParser.ParseFilter(filterQuery); } IEnumerable <OrderByToken> orderByTokens = null; string orderByQuery = queryOptions.GetQueryOptionValueAndRemove(UriQueryConstants.OrderByQueryOption); if (orderByQuery != null) { UriQueryExpressionParser expressionParser = new UriQueryExpressionParser(maxDepth); orderByTokens = expressionParser.ParseOrderBy(orderByQuery); } SelectToken select = null; string selectQuery = queryOptions.GetQueryOptionValueAndRemove(UriQueryConstants.SelectQueryOption); if (selectQuery != null) { SelectExpandParser selectParser = new SelectExpandParser(selectQuery, ODataUriParserSettings.DefaultSelectExpandLimit); select = selectParser.ParseSelect(); } ExpandToken expand = null; string expandQuery = queryOptions.GetQueryOptionValueAndRemove(UriQueryConstants.ExpandQueryOption); if (expandQuery != null) { SelectExpandParser expandParser = new SelectExpandParser(expandQuery, ODataUriParserSettings.DefaultSelectExpandLimit); expand = expandParser.ParseExpand(); } int? skip = null; string skipQuery = queryOptions.GetQueryOptionValueAndRemove(UriQueryConstants.SkipQueryOption); if (skipQuery != null) { int skipValue; if (!TryUriStringToNonNegativeInteger(skipQuery, out skipValue)) { throw new ODataException(Strings.SyntacticTree_InvalidSkipQueryOptionValue(skipQuery)); } skip = skipValue; } int? top = null; string topQuery = queryOptions.GetQueryOptionValueAndRemove(UriQueryConstants.TopQueryOption); if (topQuery != null) { int topValue; if (!TryUriStringToNonNegativeInteger(topQuery, out topValue)) { throw new ODataException(Strings.SyntacticTree_InvalidTopQueryOptionValue(topQuery)); } top = topValue; } string countQuery = queryOptions.GetQueryOptionValueAndRemove(UriQueryConstants.CountQueryOption); bool? count = ParseQueryCount(countQuery); string format = queryOptions.GetQueryOptionValueAndRemove(UriQueryConstants.FormatQueryOption); return(new SyntacticTree( parameterAliases, path, filter, orderByTokens, select, expand, skip, top, count, format, queryOptions.Count == 0 ? null : new ReadOnlyCollection <CustomQueryOptionToken>(queryOptions))); }