/// <summary> /// Retrieve CollectionNode bound with given query token. /// </summary> /// <param name="queryToken">The query token</param> /// <param name="expectedType">The expected type that this collection holds</param> /// <param name="model">The Edm model</param> /// <returns>The corresponding CollectionNode</returns> private CollectionNode GetCollectionOperandFromToken(QueryToken queryToken, IEdmTypeReference expectedType, IEdmModel model) { CollectionNode operand = null; LiteralToken literalToken = queryToken as LiteralToken; if (literalToken != null) { string originalLiteralText = literalToken.OriginalText; // Parentheses-based collections are not standard JSON but bracket-based ones are. // Temporarily switch our collection to bracket-based so that the JSON reader will // correctly parse the collection. Then pass the original literal text to the token. string bracketLiteralText = originalLiteralText; if (bracketLiteralText[0] == '(') { Debug.Assert(bracketLiteralText[bracketLiteralText.Length - 1] == ')', "Collection with opening '(' should have corresponding ')'"); if (bracketLiteralText.Replace(" ", String.Empty) == "()") { throw new ODataException(ODataErrorStrings.MetadataBinder_RightOperandNotCollectionValue); } StringBuilder replacedText = new StringBuilder(bracketLiteralText); replacedText[0] = '['; replacedText[replacedText.Length - 1] = ']'; bracketLiteralText = replacedText.ToString(); Debug.Assert(expectedType.IsCollection()); string expectedTypeFullName = expectedType.Definition.AsElementType().FullTypeName(); if (expectedTypeFullName.Equals("Edm.String", StringComparison.Ordinal)) { // For collection of strings, need to convert single-quoted string to double-quoted string, // and also, per ABNF, a single quote within a string literal is "encoded" as two consecutive single quotes in either // literal or percent - encoded representation. // Sample: ['a''bc','''def','xyz'''] ==> ["a'bc","'def","xyz'"], which is legitimate Json format. bracketLiteralText = NormalizeStringCollectionItems(bracketLiteralText); } else if (expectedTypeFullName.Equals("Edm.Guid", StringComparison.Ordinal)) { // For collection of Guids, need to convert the Guid literals to single-quoted form, so that it is compatible // with the Json reader used for deserialization. // Sample: [D01663CF-EB21-4A0E-88E0-361C10ACE7FD, 492CF54A-84C9-490C-A7A4-B5010FAD8104] // ==> ['D01663CF-EB21-4A0E-88E0-361C10ACE7FD', '492CF54A-84C9-490C-A7A4-B5010FAD8104'] bracketLiteralText = NormalizeGuidCollectionItems(bracketLiteralText); } } object collection = ODataUriConversionUtils.ConvertFromCollectionValue(bracketLiteralText, model, expectedType); LiteralToken collectionLiteralToken = new LiteralToken(collection, originalLiteralText, expectedType); operand = this.bindMethod(collectionLiteralToken) as CollectionConstantNode; } else { operand = this.bindMethod(queryToken) as CollectionNode; } if (operand == null) { throw new ODataException(ODataErrorStrings.MetadataBinder_RightOperandNotCollectionValue); } return(operand); }
/// <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: // inner $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> /// Visits a <see cref="QueryToken"/> in the lexical tree and binds it to metadata producing a semantic <see cref="QueryNode"/>. /// </summary> /// <param name="token">The query token on the input.</param> /// <returns>The bound query node output.</returns> protected internal QueryNode Bind(QueryToken token) { ExceptionUtils.CheckArgumentNotNull(token, "token"); this.BindingState.RecurseEnter(); QueryNode result; switch (token.Kind) { case QueryTokenKind.Any: result = this.BindAnyAll((AnyToken)token); break; case QueryTokenKind.All: result = this.BindAnyAll((AllToken)token); break; case QueryTokenKind.InnerPath: result = this.BindInnerPathSegment((InnerPathToken)token); break; case QueryTokenKind.Literal: result = this.BindLiteral((LiteralToken)token); break; case QueryTokenKind.StringLiteral: result = this.BindStringLiteral((StringLiteralToken)token); break; case QueryTokenKind.BinaryOperator: result = this.BindBinaryOperator((BinaryOperatorToken)token); break; case QueryTokenKind.UnaryOperator: result = this.BindUnaryOperator((UnaryOperatorToken)token); break; case QueryTokenKind.EndPath: result = this.BindEndPath((EndPathToken)token); break; case QueryTokenKind.FunctionCall: result = this.BindFunctionCall((FunctionCallToken)token); break; case QueryTokenKind.DottedIdentifier: result = this.BindCast((DottedIdentifierToken)token); break; case QueryTokenKind.RangeVariable: result = this.BindRangeVariable((RangeVariableToken)token); break; case QueryTokenKind.FunctionParameterAlias: result = this.BindParameterAlias((FunctionParameterAliasToken)token); break; case QueryTokenKind.FunctionParameter: result = this.BindFunctionParameter((FunctionParameterToken)token); break; case QueryTokenKind.In: result = this.BindIn((InToken)token); break; default: throw new ODataException(ODataErrorStrings.MetadataBinder_UnsupportedQueryTokenKind(token.Kind)); } if (result == null) { throw new ODataException(ODataErrorStrings.MetadataBinder_BoundNodeCannotBeNull(token.Kind)); } this.BindingState.RecurseLeave(); return(result); }
/// <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> /// 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; 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_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.QueryOptionCompute: { this.lexer.NextToken(); string computeText = this.ReadQueryOption(); UriQueryExpressionParser computeParser = new UriQueryExpressionParser(this.MaxOrderByDepth, enableCaseInsensitiveBuiltinIdentifier); computeOption = computeParser.ParseCompute(computeText); 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, computeOption); expandTermTokenList.Add(currentToken); return(expandTermTokenList); }
/// <summary> /// Parses the All portion of the query /// </summary> /// <param name="parent">The parent of the All node.</param> /// <returns>The lexical token representing the All query.</returns> private QueryToken ParseAll(QueryToken parent) { return(this.ParseAnyAll(parent, false)); }
/// <summary> /// Parses the Any portion of the query /// </summary> /// <param name="parent">The parent of the Any node.</param> /// <returns>The lexical token representing the Any query.</returns> private QueryToken ParseAny(QueryToken parent) { return(this.ParseAnyAll(parent, true)); }
/// <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))); }