1 //---------------------------------------------------------------------
2 // <copyright file="Literal.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
8 //---------------------------------------------------------------------
10 namespace System.Data.Common.EntitySql.AST
13 using System.Diagnostics;
14 using System.Globalization;
17 /// Defines literal value kind, including the eSQL untyped NULL.
19 internal enum LiteralKind
34 /// Represents a literal ast node.
36 internal sealed class Literal : Node
38 private readonly LiteralKind _literalKind;
39 private string _originalValue;
40 private bool _wasValueComputed = false;
41 private object _computedValue;
43 private static readonly Byte[] _emptyByteArray = new byte[0];
46 /// Initializes a literal ast node.
48 /// <param name="originalValue">literal value in cql string representation</param>
49 /// <param name="kind">literal value class</param>
50 /// <param name="query">query</param>
51 /// <param name="inputPos">input position</param>
52 internal Literal(string originalValue, LiteralKind kind, string query, int inputPos)
53 : base(query, inputPos)
55 _originalValue = originalValue;
60 /// Static factory to create boolean literals by value only.
62 /// <param name="value"></param>
63 internal static Literal NewBooleanLiteral(bool value) { return new Literal(value); }
65 private Literal(bool boolLiteral)
68 _wasValueComputed = true;
69 _originalValue = String.Empty;
70 _computedValue = boolLiteral;
71 _type = typeof(System.Boolean);
75 /// True if literal is a number.
77 internal bool IsNumber
81 return (_literalKind == LiteralKind.Number);
86 /// True if literal is a signed number.
88 internal bool IsSignedNumber
92 return IsNumber && (_originalValue[0] == '-' || _originalValue[0] == '+');
97 /// True if literal is a string.
100 /// <exception cref="System.Data.EntityException"></exception>
102 internal bool IsString
106 return _literalKind == LiteralKind.String || _literalKind == LiteralKind.UnicodeString;
111 /// True if literal is a unicode string.
114 /// <exception cref="System.Data.EntityException"></exception>
116 internal bool IsUnicodeString
120 return _literalKind == LiteralKind.UnicodeString;
125 /// True if literal is the eSQL untyped null.
128 /// <exception cref="System.Data.EntityException"></exception>
130 internal bool IsNullLiteral
134 return _literalKind == LiteralKind.Null;
139 /// Returns the original literal value.
141 internal string OriginalValue
145 return _originalValue;
150 /// Prefix a numeric literal with a sign.
152 internal void PrefixSign(string sign)
154 System.Diagnostics.Debug.Assert(IsNumber && !IsSignedNumber);
155 System.Diagnostics.Debug.Assert(sign[0] == '-' || sign[0] == '+', "sign symbol must be + or -");
156 System.Diagnostics.Debug.Assert(_computedValue == null);
158 _originalValue = sign + _originalValue;
161 #region Computed members
163 /// Returns literal converted value.
166 /// <exception cref="System.Data.EntityException"></exception>
168 internal object Value
174 return _computedValue;
179 /// Returns literal value type. If value is eSQL untyped null, returns null.
182 /// <exception cref="System.Data.EntityException"></exception>
195 private void ComputeValue()
197 if (!_wasValueComputed)
199 _wasValueComputed = true;
201 switch (_literalKind)
203 case LiteralKind.Number:
204 _computedValue = ConvertNumericLiteral(ErrCtx, _originalValue);
207 case LiteralKind.String:
208 _computedValue = GetStringLiteralValue(_originalValue, false /* isUnicode */);
211 case LiteralKind.UnicodeString:
212 _computedValue = GetStringLiteralValue(_originalValue, true /* isUnicode */);
215 case LiteralKind.Boolean:
216 _computedValue = ConvertBooleanLiteralValue(ErrCtx, _originalValue);
219 case LiteralKind.Binary:
220 _computedValue = ConvertBinaryLiteralValue(ErrCtx, _originalValue);
223 case LiteralKind.DateTime:
224 _computedValue = ConvertDateTimeLiteralValue(ErrCtx, _originalValue);
227 case LiteralKind.Time:
228 _computedValue = ConvertTimeLiteralValue(ErrCtx, _originalValue);
231 case LiteralKind.DateTimeOffset:
232 _computedValue = ConvertDateTimeOffsetLiteralValue(ErrCtx, _originalValue);
235 case LiteralKind.Guid:
236 _computedValue = ConvertGuidLiteralValue(ErrCtx, _originalValue);
239 case LiteralKind.Null:
240 _computedValue = null;
244 throw EntityUtil.NotSupported(System.Data.Entity.Strings.LiteralTypeNotSupported(_literalKind.ToString()));
248 _type = IsNullLiteral ? null : _computedValue.GetType();
252 #region Conversion Helpers
253 static char[] numberSuffixes = new char[] { 'U', 'u', 'L', 'l', 'F', 'f', 'M', 'm', 'D', 'd' };
254 static char[] floatTokens = new char[] { '.', 'E', 'e' };
255 private static object ConvertNumericLiteral(ErrorContext errCtx, string numericString)
257 int k = numericString.IndexOfAny(numberSuffixes);
260 string suffix = numericString.Substring(k).ToUpperInvariant();
261 string numberPart = numericString.Substring(0, numericString.Length - suffix.Length);
267 if (!UInt32.TryParse(numberPart, NumberStyles.Integer, CultureInfo.InvariantCulture, out value))
269 throw EntityUtil.EntitySqlError(errCtx, System.Data.Entity.Strings.CannotConvertNumericLiteral(numericString, "unsigned int"));
278 if (!Int64.TryParse(numberPart, NumberStyles.Integer, CultureInfo.InvariantCulture, out value))
280 throw EntityUtil.EntitySqlError(errCtx, System.Data.Entity.Strings.CannotConvertNumericLiteral(numericString, "long"));
290 if (!UInt64.TryParse(numberPart, NumberStyles.Integer, CultureInfo.InvariantCulture, out value))
292 throw EntityUtil.EntitySqlError(errCtx, System.Data.Entity.Strings.CannotConvertNumericLiteral(numericString, "unsigned long"));
301 if (!Single.TryParse(numberPart, NumberStyles.Float, CultureInfo.InvariantCulture, out value))
303 throw EntityUtil.EntitySqlError(errCtx, System.Data.Entity.Strings.CannotConvertNumericLiteral(numericString, "float"));
312 if (!Decimal.TryParse(numberPart, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out value))
314 throw EntityUtil.EntitySqlError(errCtx, System.Data.Entity.Strings.CannotConvertNumericLiteral(numericString, "decimal"));
323 if (!Double.TryParse(numberPart, NumberStyles.Float, CultureInfo.InvariantCulture, out value))
325 throw EntityUtil.EntitySqlError(errCtx, System.Data.Entity.Strings.CannotConvertNumericLiteral(numericString, "double"));
335 // If hit this point, try default conversion
337 return DefaultNumericConversion(numericString, errCtx);
341 /// Performs conversion of numeric strings that have no type suffix hint.
343 private static object DefaultNumericConversion(string numericString, ErrorContext errCtx)
346 if (-1 != numericString.IndexOfAny(floatTokens))
349 if (!Double.TryParse(numericString, NumberStyles.Float, CultureInfo.InvariantCulture, out value))
351 throw EntityUtil.EntitySqlError(errCtx, System.Data.Entity.Strings.CannotConvertNumericLiteral(numericString, "double"));
359 if (Int32.TryParse(numericString, NumberStyles.Integer, CultureInfo.InvariantCulture, out int32Value))
365 if (!Int64.TryParse(numericString, NumberStyles.Integer, CultureInfo.InvariantCulture, out int64Value))
367 throw EntityUtil.EntitySqlError(errCtx, System.Data.Entity.Strings.CannotConvertNumericLiteral(numericString, "long"));
376 /// Converts boolean literal value.
378 private static bool ConvertBooleanLiteralValue(ErrorContext errCtx, string booleanLiteralValue)
381 if (!Boolean.TryParse(booleanLiteralValue, out result))
383 throw EntityUtil.EntitySqlError(errCtx, System.Data.Entity.Strings.InvalidLiteralFormat("Boolean", booleanLiteralValue));
389 /// Returns the string literal value.
391 private static string GetStringLiteralValue(string stringLiteralValue, bool isUnicode)
393 Debug.Assert(stringLiteralValue.Length >= 2);
394 Debug.Assert(isUnicode == ('N' == stringLiteralValue[0]), "invalid string literal value");
396 int startIndex = (isUnicode ? 2 : 1);
397 char delimiter = stringLiteralValue[startIndex - 1];
399 // NOTE: this is not a precondition validation. This validation is for security purposes based on the
400 // paranoid assumption that all input is evil. we should not see this exception under normal
402 if (delimiter != '\'' && delimiter != '\"')
404 throw EntityUtil.EntitySqlError(System.Data.Entity.Strings.MalformedStringLiteralPayload);
409 // NOTE: this is not a precondition validation. This validation is for security purposes based on the
410 // paranoid assumption that all input is evil. we should not see this exception under normal
412 int before = stringLiteralValue.Split(new char[] { delimiter }).Length - 1;
413 Debug.Assert(before % 2 == 0, "must have an even number of delimiters in the string literal");
414 if (0 != (before % 2))
416 throw EntityUtil.EntitySqlError(System.Data.Entity.Strings.MalformedStringLiteralPayload);
420 // Extract the payload and replace escaped chars that match the envelope delimiter
422 result = stringLiteralValue.Substring(startIndex, stringLiteralValue.Length - (1 + startIndex));
423 result = result.Replace(new String(delimiter, 2), new String(delimiter, 1));
425 // NOTE: this is not a precondition validation. This validation is for security purposes based on the
426 // paranoid assumption that all input is evil. we should not see this exception under normal
428 int after = result.Split(new char[] { delimiter }).Length - 1;
429 Debug.Assert(after == (before - 2) / 2);
430 if ((after != ((before - 2) / 2)))
432 throw EntityUtil.EntitySqlError(System.Data.Entity.Strings.MalformedStringLiteralPayload);
439 /// Converts hex string to byte array.
441 private static byte[] ConvertBinaryLiteralValue(ErrorContext errCtx, string binaryLiteralValue)
443 Debug.Assert(null != binaryLiteralValue, "binaryStringLiteral must not be null");
445 if (String.IsNullOrEmpty(binaryLiteralValue))
447 return _emptyByteArray;
451 int endIndex = binaryLiteralValue.Length - 1;
452 Debug.Assert(startIndex <= endIndex, "startIndex <= endIndex");
453 int binaryStringLen = endIndex - startIndex + 1;
454 int byteArrayLen = binaryStringLen / 2;
455 bool hasOddBytes = 0 != (binaryStringLen % 2);
461 byte[] binaryValue = new byte[byteArrayLen];
465 binaryValue[arrayIndex++] = (byte)HexDigitToBinaryValue(binaryLiteralValue[startIndex++]);
468 while (startIndex < endIndex)
470 binaryValue[arrayIndex++] = (byte)((HexDigitToBinaryValue(binaryLiteralValue[startIndex++]) << 4) | HexDigitToBinaryValue(binaryLiteralValue[startIndex++]));
477 /// Parse single hex char.
478 /// PRECONDITION - hexChar must be a valid hex digit.
480 private static int HexDigitToBinaryValue(char hexChar)
482 if (hexChar >= '0' && hexChar <= '9') return (int)(hexChar - '0');
483 if (hexChar >= 'A' && hexChar <= 'F') return (int)(hexChar - 'A') + 10;
484 if (hexChar >= 'a' && hexChar <= 'f') return (int)(hexChar - 'a') + 10;
485 Debug.Assert(false, "Invalid Hexadecimal Digit");
486 throw EntityUtil.ArgumentOutOfRange("hexadecimal digit is not valid");
490 static readonly char[] _datetimeSeparators = new char[] { ' ', ':', '-', '.' };
491 static readonly char[] _dateSeparators = new char[] { '-' };
492 static readonly char[] _timeSeparators = new char[] { ':', '.' };
493 static readonly char[] _datetimeOffsetSeparators = new char[] { ' ', ':', '-', '.', '+', '-' };
496 /// Converts datetime literal value.
498 private static DateTime ConvertDateTimeLiteralValue(ErrorContext errCtx, string datetimeLiteralValue)
500 string[] datetimeParts = datetimeLiteralValue.Split(_datetimeSeparators, StringSplitOptions.RemoveEmptyEntries);
502 Debug.Assert(datetimeParts.Length >= 5, "datetime literal value must have at least 5 parts");
507 GetDateParts(datetimeLiteralValue, datetimeParts, out year, out month, out day);
512 GetTimeParts(datetimeLiteralValue, datetimeParts, 3, out hour, out minute, out second, out ticks);
514 Debug.Assert(year >= 1 && year <= 9999);
515 Debug.Assert(month >= 1 && month <= 12);
516 Debug.Assert(day >= 1 && day <= 31);
517 Debug.Assert(hour >= 0 && hour <= 24);
518 Debug.Assert(minute >= 0 && minute <= 59);
519 Debug.Assert(second >= 0 && second <= 59);
520 Debug.Assert(ticks >= 0 && ticks <= 9999999);
521 DateTime dateTime = new DateTime(year, month, day, hour, minute, second, 0);
522 dateTime = dateTime.AddTicks(ticks);
526 private static DateTimeOffset ConvertDateTimeOffsetLiteralValue(ErrorContext errCtx, string datetimeLiteralValue)
528 string[] datetimeParts = datetimeLiteralValue.Split(_datetimeOffsetSeparators, StringSplitOptions.RemoveEmptyEntries);
530 Debug.Assert(datetimeParts.Length >= 7, "datetime literal value must have at least 7 parts");
535 GetDateParts(datetimeLiteralValue, datetimeParts, out year, out month, out day);
540 //Copy the time parts into a different array since the last two parts will be handled in this method.
541 string[] timeParts = new String[datetimeParts.Length - 2];
542 Array.Copy(datetimeParts, timeParts, datetimeParts.Length - 2);
543 GetTimeParts(datetimeLiteralValue, timeParts, 3, out hour, out minute, out second, out ticks);
545 Debug.Assert(year >= 1 && year <= 9999);
546 Debug.Assert(month >= 1 && month <= 12);
547 Debug.Assert(day >= 1 && day <= 31);
548 Debug.Assert(hour >= 0 && hour <= 24);
549 Debug.Assert(minute >= 0 && minute <= 59);
550 Debug.Assert(second >= 0 && second <= 59);
551 Debug.Assert(ticks >= 0 && ticks <= 9999999);
552 int offsetHours = Int32.Parse(datetimeParts[datetimeParts.Length - 2], NumberStyles.Integer, CultureInfo.InvariantCulture);
553 int offsetMinutes = Int32.Parse(datetimeParts[datetimeParts.Length - 1], NumberStyles.Integer, CultureInfo.InvariantCulture);
554 TimeSpan offsetTimeSpan = new TimeSpan(offsetHours, offsetMinutes, 0);
556 //If DateTimeOffset had a negative offset, we should negate the timespan
557 if (datetimeLiteralValue.IndexOf('+') == -1)
559 offsetTimeSpan = offsetTimeSpan.Negate();
561 DateTime dateTime = new DateTime(year, month, day, hour, minute, second, 0);
562 dateTime = dateTime.AddTicks(ticks);
566 return new DateTimeOffset(dateTime, offsetTimeSpan);
568 catch (ArgumentOutOfRangeException e)
570 throw EntityUtil.EntitySqlError(errCtx, System.Data.Entity.Strings.InvalidDateTimeOffsetLiteral(datetimeLiteralValue), e);
575 /// Converts time literal value.
577 private static TimeSpan ConvertTimeLiteralValue(ErrorContext errCtx, string datetimeLiteralValue)
579 string[] datetimeParts = datetimeLiteralValue.Split(_datetimeSeparators, StringSplitOptions.RemoveEmptyEntries);
581 Debug.Assert(datetimeParts.Length >= 2, "time literal value must have at least 2 parts");
587 GetTimeParts(datetimeLiteralValue, datetimeParts, 0, out hour, out minute, out second, out ticks);
589 Debug.Assert(hour >= 0 && hour <= 24);
590 Debug.Assert(minute >= 0 && minute <= 59);
591 Debug.Assert(second >= 0 && second <= 59);
592 Debug.Assert(ticks >= 0 && ticks <= 9999999);
593 TimeSpan ts = new TimeSpan(hour, minute, second);
594 ts = ts.Add(new TimeSpan(ticks));
598 private static void GetTimeParts(string datetimeLiteralValue, string[] datetimeParts, int timePartStartIndex, out int hour, out int minute, out int second, out int ticks)
600 hour = Int32.Parse(datetimeParts[timePartStartIndex], NumberStyles.Integer, CultureInfo.InvariantCulture);
603 throw EntityUtil.EntitySqlError(System.Data.Entity.Strings.InvalidHour(datetimeParts[timePartStartIndex], datetimeLiteralValue));
605 minute = Int32.Parse(datetimeParts[++timePartStartIndex], NumberStyles.Integer, CultureInfo.InvariantCulture);
608 throw EntityUtil.EntitySqlError(System.Data.Entity.Strings.InvalidMinute(datetimeParts[timePartStartIndex], datetimeLiteralValue));
612 timePartStartIndex++;
613 if (datetimeParts.Length > timePartStartIndex)
615 second = Int32.Parse(datetimeParts[timePartStartIndex], NumberStyles.Integer, CultureInfo.InvariantCulture);
618 throw EntityUtil.EntitySqlError(System.Data.Entity.Strings.InvalidSecond(datetimeParts[timePartStartIndex], datetimeLiteralValue));
620 timePartStartIndex++;
621 if (datetimeParts.Length > timePartStartIndex)
623 //We need fractional time part to be seven digits
624 string ticksString = datetimeParts[timePartStartIndex].PadRight(7, '0');
625 ticks = Int32.Parse(ticksString, NumberStyles.Integer, CultureInfo.InvariantCulture);
631 private static void GetDateParts(string datetimeLiteralValue, string[] datetimeParts, out int year, out int month, out int day)
633 year = Int32.Parse(datetimeParts[0], NumberStyles.Integer, CultureInfo.InvariantCulture);
634 if (year < 1 || year > 9999)
636 throw EntityUtil.EntitySqlError(System.Data.Entity.Strings.InvalidYear(datetimeParts[0], datetimeLiteralValue));
638 month = Int32.Parse(datetimeParts[1], NumberStyles.Integer, CultureInfo.InvariantCulture);
639 if (month < 1 || month > 12)
641 throw EntityUtil.EntitySqlError(System.Data.Entity.Strings.InvalidMonth(datetimeParts[1], datetimeLiteralValue));
643 day = Int32.Parse(datetimeParts[2], NumberStyles.Integer, CultureInfo.InvariantCulture);
646 throw EntityUtil.EntitySqlError(System.Data.Entity.Strings.InvalidDay(datetimeParts[2], datetimeLiteralValue));
648 if (day > DateTime.DaysInMonth(year, month))
650 throw EntityUtil.EntitySqlError(System.Data.Entity.Strings.InvalidDayInMonth(datetimeParts[2], datetimeParts[1], datetimeLiteralValue));
655 /// Converts guid literal value.
657 private static Guid ConvertGuidLiteralValue(ErrorContext errCtx, string guidLiteralValue)
659 return new Guid(guidLiteralValue);