예제 #1
0
 /// <summary>Validates the current token is of the specified kind.</summary>
 /// <param name="t">Expected token kind.</param>
 internal void ValidateToken(ExpressionTokenKind t)
 {
     if (this.token.Kind != t)
     {
         throw ParseError(ODataErrorStrings.ExpressionLexer_SyntaxError(this.textPos, this.Text));
     }
 }
예제 #2
0
 /// <summary>
 /// Get type-prefixed literals with quoted values duration, binary and spatial types.
 /// </summary>
 /// <param name="tokenText">Token text</param>
 /// <returns>ExpressionTokenKind</returns>
 /// <example>geometry'POINT (79 84)'. 'geometry' is the tokenText </example>
 private ExpressionTokenKind GetBuiltInTypesLiteralPrefixWithQuotedValue(string tokenText)
 {
     if (String.Equals(tokenText, ExpressionConstants.LiteralPrefixDuration, StringComparison.OrdinalIgnoreCase))
     {
         return(ExpressionTokenKind.DurationLiteral);
     }
     else if (String.Equals(tokenText, ExpressionConstants.LiteralPrefixBinary, StringComparison.OrdinalIgnoreCase))
     {
         return(ExpressionTokenKind.BinaryLiteral);
     }
     else if (String.Equals(tokenText, ExpressionConstants.LiteralPrefixGeography, StringComparison.OrdinalIgnoreCase))
     {
         return(ExpressionTokenKind.GeographyLiteral);
     }
     else if (String.Equals(tokenText, ExpressionConstants.LiteralPrefixGeometry, StringComparison.OrdinalIgnoreCase))
     {
         return(ExpressionTokenKind.GeometryLiteral);
     }
     else if (string.Equals(tokenText, ExpressionConstants.KeywordNull, StringComparison.OrdinalIgnoreCase))
     {
         // typed null literals are not supported.
         throw ParseError(ODataErrorStrings.ExpressionLexer_SyntaxError(this.textPos, this.Text));
     }
     else
     {
         // treat as quoted literal
         return(ExpressionTokenKind.QuotedLiteral);
     }
 }
예제 #3
0
 /// <summary>Validates the current character is a digit.</summary>
 private void ValidateDigit()
 {
     if (!this.IsValidDigit)
     {
         throw ParseError(ODataErrorStrings.ExpressionLexer_DigitExpected(this.textPos, this.Text));
     }
 }
예제 #4
0
        public void UnsupportedTokenBinderErrorTest()
        {
            IEdmModel model  = QueryTestMetadata.BuildTestMetadata(this.PrimitiveTypeResolver, this.UntypedDataServiceProviderFactory);
            var       binder = new ErrorMetadataBinder(model);
            var       token  = new LiteralToken("d");
            Action    action = () => binder.Bind(token);

            action.ShouldThrow <ODataException>().WithMessage(ODataErrorStrings.MetadataBinder_BoundNodeCannotBeNull(token.Kind));
        }
예제 #5
0
        /// <summary>
        /// Create a InNode
        /// </summary>
        /// <param name="left">The left operand.</param>
        /// <param name="right">The right operand.</param>
        /// <exception cref="System.ArgumentNullException">Throws if the left or right inputs are null.</exception>
        /// <exception cref="ODataException">Throws if the right operand single item type isn't the same type as the left operand.</exception>
        public InNode(SingleValueNode left, CollectionNode right)
        {
            ExceptionUtils.CheckArgumentNotNull(left, "left");
            ExceptionUtils.CheckArgumentNotNull(right, "right");
            this.left  = left;
            this.right = right;

            if (!this.left.GetEdmTypeReference().IsAssignableFrom(this.right.ItemType) &&
                !this.right.ItemType.IsAssignableFrom(this.left.GetEdmTypeReference()))
            {
                throw new ArgumentException(ODataErrorStrings.Nodes_InNode_CollectionItemTypeMustBeSameAsSingleItemType(
                                                this.right.ItemType.FullName(), this.left.GetEdmTypeReference().FullName()));
            }
        }
예제 #6
0
        /// <summary>
        /// Starting from an identifier, reads a sequence of dots and
        /// identifiers, and returns the text for it, with whitespace
        /// stripped.
        /// </summary>
        /// <param name="acceptStar">do we allow a star in this identifier</param>
        /// <returns>The dotted identifier starting at the current identifier.</returns>
        internal string ReadDottedIdentifier(bool acceptStar)
        {
            this.ValidateToken(ExpressionTokenKind.Identifier);
            StringBuilder builder = null;
            string        result  = this.CurrentToken.Text;

            this.NextToken();
            while (this.CurrentToken.Kind == ExpressionTokenKind.Dot)
            {
                this.NextToken();
                if (this.CurrentToken.Kind != ExpressionTokenKind.Identifier &&
                    this.CurrentToken.Kind != ExpressionTokenKind.QuotedLiteral)
                {
                    if (this.CurrentToken.Kind == ExpressionTokenKind.Star)
                    {
                        // if we accept a star and this is the last token in the identifier, then we're ok... otherwise we throw.
                        if (!acceptStar || (this.PeekNextToken().Kind != ExpressionTokenKind.End && this.PeekNextToken().Kind != ExpressionTokenKind.Comma))
                        {
                            throw ParseError(ODataErrorStrings.ExpressionLexer_SyntaxError(this.textPos, this.Text));
                        }
                    }
                    else
                    {
                        throw ParseError(ODataErrorStrings.ExpressionLexer_SyntaxError(this.textPos, this.Text));
                    }
                }

                if (builder == null)
                {
                    builder = new StringBuilder(result, result.Length + 1 + this.CurrentToken.Text.Length);
                }

                builder.Append('.');
                builder.Append(this.CurrentToken.Text);
                this.NextToken();
            }

            if (builder != null)
            {
                result = builder.ToString();
            }

            return(result);
        }
예제 #7
0
        private void HandleQuotedValues()
        {
            int startPosition = this.token.Position;

            do
            {
                do
                {
                    this.NextChar();
                }while (this.ch.HasValue && this.ch != '\'');

                if (this.ch == null)
                {
                    throw ParseError(ODataErrorStrings.ExpressionLexer_UnterminatedLiteral(this.textPos, this.Text));
                }

                this.NextChar();
            }while (this.ch.HasValue && this.ch == '\'');

            // Update token.Text to include the literal + the quoted value
            this.token.Text = this.Text.Substring(startPosition, this.textPos - startPosition);
        }
예제 #8
0
        /// <summary>
        /// If the source node is not of the specified type, then we check if type promotion is possible and inject a convert node.
        /// If the source node is the same type as the target type (or if the target type is null), we just return the source node as is.
        /// </summary>
        /// <param name="source">The source node to apply the conversion to.</param>
        /// <param name="targetTypeReference">The target primitive type. May be null - this method will do nothing in that case.</param>
        /// <returns>The converted query node, or the original source node unchanged.</returns>
        internal static SingleValueNode ConvertToTypeIfNeeded(SingleValueNode source, IEdmTypeReference targetTypeReference)
        {
            Debug.Assert(source != null, "source != null");

            if (targetTypeReference == null)
            {
                return(source);
            }

            if (source.TypeReference != null)
            {
                if (source.TypeReference.IsEquivalentTo(targetTypeReference))
                {
                    // For source is type definition, if source's underlying type == target type.
                    // We create a conversion node from source to its underlying type (target type)
                    // so that the service can convert value of source clr type to underlying clr type.
                    if (source.TypeReference.IsTypeDefinition())
                    {
                        return(new ConvertNode(source, targetTypeReference));
                    }

                    return(source);
                }

                // Structured type in url will be translated into a node with raw string value.
                // We create a conversion node from string to structured type.
                if (targetTypeReference.IsStructured() || targetTypeReference.IsStructuredCollectionType())
                {
                    return(new ConvertNode(source, targetTypeReference));
                }

                ConstantNode constantNode = source as ConstantNode;
                if (constantNode != null && constantNode.Value != null && source.TypeReference.IsString() && targetTypeReference.IsEnum())
                {
                    string       memberName = constantNode.Value.ToString();
                    IEdmEnumType enumType   = targetTypeReference.Definition as IEdmEnumType;
                    if (enumType.Members.Any(m => string.Compare(m.Name, memberName, StringComparison.Ordinal) == 0))
                    {
                        string literalText = ODataUriUtils.ConvertToUriLiteral(constantNode.Value, default(ODataVersion));
                        return(new ConstantNode(new ODataEnumValue(constantNode.Value.ToString(), targetTypeReference.Definition.ToString()), literalText, targetTypeReference));
                    }
                    else
                    {
                        throw new ODataException(ODataErrorStrings.Binder_IsNotValidEnumConstant(memberName));
                    }
                }

                if (!TypePromotionUtils.CanConvertTo(source, source.TypeReference, targetTypeReference))
                {
                    throw new ODataException(ODataErrorStrings.MetadataBinder_CannotConvertToType(source.TypeReference.FullName(), targetTypeReference.FullName()));
                }
                else
                {
                    if (source.TypeReference.IsEnum() && constantNode != null)
                    {
                        return(new ConstantNode(constantNode.Value, ODataUriUtils.ConvertToUriLiteral(constantNode.Value, ODataVersion.V4), targetTypeReference));
                    }

                    object originalPrimitiveValue;
                    if (MetadataUtilsCommon.TryGetConstantNodePrimitiveLDMF(source, out originalPrimitiveValue) && (originalPrimitiveValue != null))
                    {
                        // L F D M types : directly create a ConvertNode.
                        // 1. NodeToExpressionTranslator.cs won't allow implicitly converting single/double to decimal, which should be done here at Node tree level.
                        // 2. And prevent losing precision in float -> double, e.g. (double)1.234f => 1.2339999675750732d not 1.234d
                        object targetPrimitiveValue = ODataUriConversionUtils.CoerceNumericType(originalPrimitiveValue, targetTypeReference.AsPrimitive().Definition as IEdmPrimitiveType);

                        if (string.IsNullOrEmpty(constantNode.LiteralText))
                        {
                            return(new ConstantNode(targetPrimitiveValue));
                        }

                        var candidate   = new ConstantNode(targetPrimitiveValue, constantNode.LiteralText);
                        var decimalType = candidate.TypeReference as IEdmDecimalTypeReference;
                        if (decimalType != null)
                        {
                            var targetDecimalType = (IEdmDecimalTypeReference)targetTypeReference;
                            return(decimalType.Precision == targetDecimalType.Precision &&
                                   decimalType.Scale == targetDecimalType.Scale ?
                                   (SingleValueNode)candidate :
                                   (SingleValueNode)(new ConvertNode(candidate, targetTypeReference)));
                        }
                        else
                        {
                            return(candidate);
                        }
                    }
                    else
                    {
                        // other type conversion : ConvertNode
                        return(new ConvertNode(source, targetTypeReference));
                    }
                }
            }
            else
            {
                // If the source doesn't have a type (possibly an open property), then it's possible to convert it
                // cause we don't know for sure.
                return(new ConvertNode(source, targetTypeReference));
            }
        }
예제 #9
0
        protected virtual ExpressionToken NextTokenImplementation(out Exception error)
        {
            error = null;

            if (this.ignoreWhitespace)
            {
                this.ParseWhitespace();
            }

            ExpressionTokenKind t;
            int tokenPos = this.textPos;

            switch (this.ch)
            {
            case '(':
                this.NextChar();
                t = ExpressionTokenKind.OpenParen;
                break;

            case ')':
                this.NextChar();
                t = ExpressionTokenKind.CloseParen;
                break;

            case ',':
                this.NextChar();
                t = ExpressionTokenKind.Comma;
                break;

            case '-':
                bool hasNext = this.textPos + 1 < this.TextLen;
                if (hasNext && Char.IsDigit(this.Text[this.textPos + 1]))
                {
                    // don't separate '-' and its following digits : -2147483648 is valid int.MinValue, but 2147483648 is long.
                    t = this.ParseFromDigit();
                    if (ExpressionLexerUtils.IsNumeric(t))
                    {
                        break;
                    }

                    // If it looked like a numeric but wasn't (because it was a binary 0x... value for example),
                    // we'll rewind and fall through to a simple '-' token.
                    this.SetTextPos(tokenPos);
                }
                else if (hasNext && this.Text[tokenPos + 1] == ExpressionConstants.InfinityLiteral[0])
                {
                    this.NextChar();
                    this.ParseIdentifier();
                    string currentIdentifier = this.Text.Substring(tokenPos + 1, this.textPos - tokenPos - 1);

                    if (ExpressionLexerUtils.IsInfinityLiteralDouble(currentIdentifier))
                    {
                        t = ExpressionTokenKind.DoubleLiteral;
                        break;
                    }
                    else if (ExpressionLexerUtils.IsInfinityLiteralSingle(currentIdentifier))
                    {
                        t = ExpressionTokenKind.SingleLiteral;
                        break;
                    }

                    // If it looked like '-INF' but wasn't we'll rewind and fall through to a simple '-' token.
                    this.SetTextPos(tokenPos);
                }

                this.NextChar();
                t = ExpressionTokenKind.Minus;
                break;

            case '=':
                this.NextChar();
                t = ExpressionTokenKind.Equal;
                break;

            case '/':
                this.NextChar();
                t = ExpressionTokenKind.Slash;
                break;

            case '?':
                this.NextChar();
                t = ExpressionTokenKind.Question;
                break;

            case '.':
                this.NextChar();
                t = ExpressionTokenKind.Dot;
                break;

            case '\'':
                char quote = this.ch.Value;
                do
                {
                    this.AdvanceToNextOccuranceOf(quote);

                    if (this.textPos == this.TextLen)
                    {
                        error = ParseError(ODataErrorStrings.ExpressionLexer_UnterminatedStringLiteral(this.textPos, this.Text));
                    }

                    this.NextChar();
                }while (this.ch.HasValue && (this.ch.Value == quote));
                t = ExpressionTokenKind.StringLiteral;
                break;

            case '*':
                this.NextChar();
                t = ExpressionTokenKind.Star;
                break;

            case ':':
                this.NextChar();
                t = ExpressionTokenKind.Colon;
                break;

            case '{':
                this.NextChar();
                this.AdvanceThroughBalancedExpression('{', '}');
                t = ExpressionTokenKind.BracedExpression;
                break;

            case '[':
                this.NextChar();
                this.AdvanceThroughBalancedExpression('[', ']');
                t = ExpressionTokenKind.BracketedExpression;
                break;

            default:
                if (this.IsValidWhiteSpace)
                {
                    Debug.Assert(!this.ignoreWhitespace, "should not hit ws while ignoring it");
                    this.ParseWhitespace();
                    t = ExpressionTokenKind.Unknown;
                    break;
                }

                if (this.IsValidStartingCharForIdentifier)
                {
                    this.ParseIdentifier();

                    // Guids will have '-' in them
                    // guidValue = 8HEXDIG "-" 4HEXDIG "-" 4HEXDIG "-" 4HEXDIG "-" 12HEXDIG
                    if (this.ch == '-' &&
                        this.TryParseGuid(tokenPos))
                    {
                        t = ExpressionTokenKind.GuidLiteral;
                        break;
                    }

                    t = ExpressionTokenKind.Identifier;
                    break;
                }

                if (this.IsValidDigit)
                {
                    t = this.ParseFromDigit();
                    break;
                }

                if (this.textPos == this.TextLen)
                {
                    t = ExpressionTokenKind.End;
                    break;
                }

                if (this.useSemicolonDelimeter && this.ch == ';')
                {
                    this.NextChar();
                    t = ExpressionTokenKind.SemiColon;
                    break;
                }

                if (this.parsingFunctionParameters && this.ch == '@')
                {
                    this.NextChar();

                    if (this.textPos == this.TextLen)
                    {
                        error = ParseError(ODataErrorStrings.ExpressionLexer_SyntaxError(this.textPos, this.Text));
                        t     = ExpressionTokenKind.Unknown;
                        break;
                    }

                    if (!this.IsValidStartingCharForIdentifier)
                    {
                        error = ParseError(ODataErrorStrings.ExpressionLexer_InvalidCharacter(this.ch, this.textPos, this.Text));
                        t     = ExpressionTokenKind.Unknown;
                        break;
                    }

                    this.ParseIdentifier();
                    t = ExpressionTokenKind.ParameterAlias;
                    break;
                }

                error = ParseError(ODataErrorStrings.ExpressionLexer_InvalidCharacter(this.ch, this.textPos, this.Text));
                t     = ExpressionTokenKind.Unknown;
                break;
            }

            this.token.Kind     = t;
            this.token.Text     = this.Text.Substring(tokenPos, this.textPos - tokenPos);
            this.token.Position = tokenPos;

            this.HandleTypePrefixedLiterals();

            return(this.token);
        }
예제 #10
0
        /// <summary>
        /// Makes best guess on numeric string without trailing letter like L, F, M, D
        /// </summary>
        /// <param name="numericStr">The numeric string.</param>
        /// <param name="guessedKind">The possbile kind (IntegerLiteral or DoubleLiteral) from ParseFromDigit() method.</param>
        /// <returns>A more accurate ExpressionTokenKind</returns>
        private static ExpressionTokenKind MakeBestGuessOnNoSuffixStr(string numericStr, ExpressionTokenKind guessedKind)
        {
            // no suffix, so
            // (1) make a best guess (note: later we support promoting each to later one: int32->int64->single->double->decimal).
            // look at value:       "2147483647" may be Int32/long, "2147483649" must be long.
            // look at precision:   "3258.67876576549" may be sinle/double/decimal, "3258.678765765489753678965390" must be decimal.
            // (2) then let MetadataUtilsCommon.CanConvertPrimitiveTypeTo() method does further promotion when knowing expected sematics type.
            int     tmpInt     = 0;
            long    tmpLong    = 0;
            float   tmpFloat   = 0;
            double  tmpDouble  = 0;
            decimal tmpDecimal = 0;

            if (guessedKind == ExpressionTokenKind.IntegerLiteral)
            {
                if (int.TryParse(numericStr, NumberStyles.Integer, CultureInfo.InvariantCulture, out tmpInt))
                {
                    return(ExpressionTokenKind.IntegerLiteral);
                }

                if (long.TryParse(numericStr, NumberStyles.Integer, CultureInfo.InvariantCulture, out tmpLong))
                {
                    return(ExpressionTokenKind.Int64Literal);
                }
            }

            bool canBeSingle  = float.TryParse(numericStr, NumberStyles.Float, CultureInfo.InvariantCulture, out tmpFloat);
            bool canBeDouble  = double.TryParse(numericStr, NumberStyles.Float, CultureInfo.InvariantCulture, out tmpDouble);
            bool canBeDecimal = decimal.TryParse(numericStr, NumberStyles.Integer | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out tmpDecimal);

            // 1. try high precision -> low precision
            if (canBeDouble && canBeDecimal)
            {
                decimal doubleToDecimalR;
                decimal doubleToDecimalN;

                // To keep the full precision of the current value, which if necessary is all 17 digits of precision supported by the Double type.
                bool doubleCanBeDecimalR = decimal.TryParse(tmpDouble.ToString("R", CultureInfo.InvariantCulture), NumberStyles.Float, CultureInfo.InvariantCulture, out doubleToDecimalR);

                // To cover the scientific notation case, such as 1e+19 in the tmpDouble
                bool doubleCanBeDecimalN = decimal.TryParse(tmpDouble.ToString("N29", CultureInfo.InvariantCulture), NumberStyles.Number, CultureInfo.InvariantCulture, out doubleToDecimalN);

                if ((doubleCanBeDecimalR && doubleToDecimalR != tmpDecimal) || (!doubleCanBeDecimalR && doubleCanBeDecimalN && doubleToDecimalN != tmpDecimal))
                {
                    // losing precision as double, so choose decimal
                    return(ExpressionTokenKind.DecimalLiteral);
                }
            }

            // here can't use normal casting like the above double VS decimal.
            // prevent losing precision in float -> double, e.g. (double)1.234f will be 1.2339999675750732d not 1.234d
            if (canBeSingle && canBeDouble && (double.Parse(tmpFloat.ToString("R", CultureInfo.InvariantCulture), CultureInfo.InvariantCulture) != tmpDouble))
            {
                // losing precision as single, so choose double
                return(ExpressionTokenKind.DoubleLiteral);
            }

            // 2. try most compatible -> least compatible
            if (canBeSingle)
            {
                return(ExpressionTokenKind.SingleLiteral);
            }

            if (canBeDouble)
            {
                return(ExpressionTokenKind.DoubleLiteral);
            }

            throw new ODataException(ODataErrorStrings.ExpressionLexer_InvalidNumericString(numericStr));
        }