/// <summary>
        /// Parses the <paramref name="search"/> clause, binding
        /// the text into a metadata-bound list of properties to be selected using the provided model.
        /// </summary>
        /// <param name="search">String representation of the search expression from the URI.</param>
        /// <param name="configuration">The configuration used for binding.</param>
        /// <returns>A <see cref="SearchClause"/> representing the metadata bound search expression.</returns>
        private static SearchClause ParseSearchImplementation(string search, ODataUriParserConfiguration configuration)
        {
            ExceptionUtils.CheckArgumentNotNull(configuration, "configuration");
            ExceptionUtils.CheckArgumentNotNull(search, "search");

            SearchParser searchParser = new SearchParser(configuration.Settings.SearchLimit);
            QueryToken queryToken = searchParser.ParseSearch(search);

            // Bind it to metadata
            BindingState state = new BindingState(configuration);
            MetadataBinder binder = new MetadataBinder(state);
            SearchBinder searchBinder = new SearchBinder(binder.Bind);

            return searchBinder.BindSearch(queryToken);
        }
        /// <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>An expand term token based on the path token, and all available expand options.</returns>
        internal ExpandTermToken BuildExpandTermToken(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;
            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;
                    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:
                            {
                                // advance to the equal sign
                                this.lexer.NextToken();
                                string levelsText = this.ReadQueryOption();
                                long level;

                                if (string.Equals(
                                    ExpressionConstants.KeywordMax,
                                    levelsText,
                                    this.enableCaseInsensitiveBuiltinIdentifier ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
                                {
                                    levelsOption = long.MinValue;
                                }
                                else if (!long.TryParse(levelsText, out level) || level < 0)
                                {
                                    throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidLevelsOption(levelsText));
                                }
                                else
                                {
                                    levelsOption = level;
                                }

                                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();

                                SelectExpandParser innerExpandParser = new SelectExpandParser(expandText, this.maxRecursionDepth - 1, enableCaseInsensitiveBuiltinIdentifier);
                                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));
            }

            return new ExpandTermToken(pathToken, filterOption, orderByOptions, topOption, skipOption, countOption, levelsOption, searchOption, selectOption, expandOption);
        }
        /// <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;
                    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, enableCaseInsensitiveBuiltinIdentifier);
                                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;
        }
Beispiel #4
0
        /// <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>An expand term token based on the path token, and all available expand options.</returns>
        internal ExpandTermToken BuildExpandTermToken(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;
            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)
                {
                    switch (this.lexer.CurrentToken.Text)
                    {
                    case ExpressionConstants.QueryOptionFilter:
                    {
                        // advance to the equal sign
                        this.lexer.NextToken();
                        string filterText = this.ReadQueryOption();

                        UriQueryExpressionParser filterParser = new UriQueryExpressionParser(this.MaxFilterDepth);
                        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);
                        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:
                    {
                        // advance to the equal sign
                        this.lexer.NextToken();
                        string levelsText = this.ReadQueryOption();
                        long   level;

                        if (string.CompareOrdinal(ExpressionConstants.KeywordMax, levelsText) == 0)
                        {
                            levelsOption = long.MinValue;
                        }
                        else if (!long.TryParse(levelsText, out level) || level < 0)
                        {
                            throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidLevelsOption(levelsText));
                        }
                        else
                        {
                            levelsOption = level;
                        }

                        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);
                        selectOption = innerSelectParser.ParseSelect();
                        break;
                    }

                    case ExpressionConstants.QueryOptionExpand:
                    {
                        // advance to the equal sign
                        this.lexer.NextToken();
                        string expandText = this.ReadQueryOption();

                        SelectExpandParser innerExpandParser = new SelectExpandParser(expandText, this.maxRecursionDepth - 1);
                        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));
            }

            return(new ExpandTermToken(pathToken, filterOption, orderByOptions, topOption, skipOption, countOption, levelsOption, searchOption, selectOption, expandOption));
        }
Beispiel #5
0
        /// <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;
                    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, enableCaseInsensitiveBuiltinIdentifier);
                        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);
        }