/******************************************************** * ADO.NET 2.0 Data Provider for SQLite Version 3.X * Written by Robert Simpson (robert@blackcastlesoft.com) * * Released to the public domain, use at your own risk! ********************************************************/ namespace System.Data.SQLite { using System; #if !NET_COMPACT_20 && TRACE_WARNING using System.Diagnostics; #endif using System.Runtime.InteropServices; using System.Collections.Generic; using System.Globalization; using System.Text; /// /// This base class provides datatype conversion services for the SQLite provider. /// public abstract class SQLiteConvert { /// /// The fallback default database type when one cannot be obtained from an /// existing connection instance. /// private const DbType FallbackDefaultDbType = DbType.Object; /// /// The fallback default database type name when one cannot be obtained from /// an existing connection instance. /// private static readonly string FallbackDefaultTypeName = String.Empty; /// /// The value for the Unix epoch (e.g. January 1, 1970 at midnight, in UTC). /// protected static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); #pragma warning disable 414 /// /// The value of the OLE Automation epoch represented as a Julian day. This /// field cannot be removed as the test suite relies upon it. /// private static readonly double OleAutomationEpochAsJulianDay = 2415018.5; #pragma warning restore 414 /// /// The format string for DateTime values when using the InvariantCulture or CurrentCulture formats. /// private const string FullFormat = "yyyy-MM-ddTHH:mm:ss.fffffffK"; /// /// This is the minimum Julian Day value supported by this library /// (148731163200000). /// private static readonly long MinimumJd = computeJD(DateTime.MinValue); /// /// This is the maximum Julian Day value supported by this library /// (464269060799000). /// private static readonly long MaximumJd = computeJD(DateTime.MaxValue); /// /// An array of ISO-8601 DateTime formats that we support parsing. /// private static string[] _datetimeFormats = new string[] { "THHmmssK", "THHmmK", "HH:mm:ss.FFFFFFFK", "HH:mm:ssK", "HH:mmK", "yyyy-MM-dd HH:mm:ss.FFFFFFFK", /* NOTE: UTC default (5). */ "yyyy-MM-dd HH:mm:ssK", "yyyy-MM-dd HH:mmK", "yyyy-MM-ddTHH:mm:ss.FFFFFFFK", "yyyy-MM-ddTHH:mmK", "yyyy-MM-ddTHH:mm:ssK", "yyyyMMddHHmmssK", "yyyyMMddHHmmK", "yyyyMMddTHHmmssFFFFFFFK", "THHmmss", "THHmm", "HH:mm:ss.FFFFFFF", "HH:mm:ss", "HH:mm", "yyyy-MM-dd HH:mm:ss.FFFFFFF", /* NOTE: Non-UTC default (19). */ "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM-ddTHH:mm:ss.FFFFFFF", "yyyy-MM-ddTHH:mm", "yyyy-MM-ddTHH:mm:ss", "yyyyMMddHHmmss", "yyyyMMddHHmm", "yyyyMMddTHHmmssFFFFFFF", "yyyy-MM-dd", "yyyyMMdd", "yy-MM-dd" }; /// /// The internal default format for UTC DateTime values when converting /// to a string. /// private static readonly string _datetimeFormatUtc = _datetimeFormats[5]; /// /// The internal default format for local DateTime values when converting /// to a string. /// private static readonly string _datetimeFormatLocal = _datetimeFormats[19]; /// /// An UTF-8 Encoding instance, so we can convert strings to and from UTF-8 /// private static Encoding _utf8 = new UTF8Encoding(); /// /// The default DateTime format for this instance. /// internal SQLiteDateFormats _datetimeFormat; /// /// The default DateTimeKind for this instance. /// internal DateTimeKind _datetimeKind; /// /// The default DateTime format string for this instance. /// internal string _datetimeFormatString = null; /// /// Initializes the conversion class /// /// The default date/time format to use for this instance /// The DateTimeKind to use. /// The DateTime format string to use. internal SQLiteConvert( SQLiteDateFormats fmt, DateTimeKind kind, string fmtString ) { _datetimeFormat = fmt; _datetimeKind = kind; _datetimeFormatString = fmtString; } #region UTF-8 Conversion Functions /// /// Converts a string to a UTF-8 encoded byte array sized to include a null-terminating character. /// /// The string to convert to UTF-8 /// A byte array containing the converted string plus an extra 0 terminating byte at the end of the array. public static byte[] ToUTF8(string sourceText) { Byte[] byteArray; int nlen = _utf8.GetByteCount(sourceText) + 1; byteArray = new byte[nlen]; nlen = _utf8.GetBytes(sourceText, 0, sourceText.Length, byteArray, 0); byteArray[nlen] = 0; return byteArray; } /// /// Convert a DateTime to a UTF-8 encoded, zero-terminated byte array. /// /// /// This function is a convenience function, which first calls ToString() on the DateTime, and then calls ToUTF8() with the /// string result. /// /// The DateTime to convert. /// The UTF-8 encoded string, including a 0 terminating byte at the end of the array. public byte[] ToUTF8(DateTime dateTimeValue) { return ToUTF8(ToString(dateTimeValue)); } /// /// Converts a UTF-8 encoded IntPtr of the specified length into a .NET string /// /// The pointer to the memory where the UTF-8 string is encoded /// The number of bytes to decode /// A string containing the translated character(s) public virtual string ToString(IntPtr nativestring, int nativestringlen) { return UTF8ToString(nativestring, nativestringlen); } /// /// Converts a UTF-8 encoded IntPtr of the specified length into a .NET string /// /// The pointer to the memory where the UTF-8 string is encoded /// The number of bytes to decode /// A string containing the translated character(s) public static string UTF8ToString(IntPtr nativestring, int nativestringlen) { if (nativestring == IntPtr.Zero || nativestringlen == 0) return String.Empty; if (nativestringlen < 0) { nativestringlen = 0; while (Marshal.ReadByte(nativestring, nativestringlen) != 0) nativestringlen++; if (nativestringlen == 0) return String.Empty; } byte[] byteArray = new byte[nativestringlen]; Marshal.Copy(nativestring, byteArray, 0, nativestringlen); return _utf8.GetString(byteArray, 0, nativestringlen); } #endregion /////////////////////////////////////////////////////////////////////////// #region DateTime Conversion Functions #region New Julian Day Conversion Methods /// /// Checks if the specified is within the /// supported range for a Julian Day value. /// /// /// The Julian Day value to check. /// /// /// Non-zero if the specified Julian Day value is in the supported /// range; otherwise, zero. /// private static bool isValidJd( long jd ) { return ((jd >= MinimumJd) && (jd <= MaximumJd)); } /////////////////////////////////////////////////////////////////////////// /// /// Converts a Julian Day value from a to an /// . /// /// /// The Julian Day value to convert. /// /// /// The resulting Julian Day value. /// private static long DoubleToJd( double julianDay ) { return (long)(julianDay * 86400000.0); } /////////////////////////////////////////////////////////////////////////// /// /// Converts a Julian Day value from an to a /// . /// /// /// The Julian Day value to convert. /// /// /// The resulting Julian Day value. /// private static double JdToDouble( long jd ) { return (double)(jd / 86400000.0); } /////////////////////////////////////////////////////////////////////////// /// /// Converts a Julian Day value to a . /// This method was translated from the "computeYMD" function in the /// "date.c" file belonging to the SQLite core library. /// /// /// The Julian Day value to convert. /// /// /// The value to return in the event that the /// Julian Day is out of the supported range. If this value is null, /// an exception will be thrown instead. /// /// /// A value that contains the year, month, and /// day values that are closest to the specified Julian Day value. /// private static DateTime computeYMD( long jd, DateTime? badValue ) { if (!isValidJd(jd)) { if (badValue == null) { throw new ArgumentException( "Not a supported Julian Day value."); } return (DateTime)badValue; } int Z, A, B, C, D, E, X1; Z = (int)((jd + 43200000) / 86400000); A = (int)((Z - 1867216.25) / 36524.25); A = Z + 1 + A - (A / 4); B = A + 1524; C = (int)((B - 122.1) / 365.25); D = (36525 * C) / 100; E = (int)((B - D) / 30.6001); X1 = (int)(30.6001 * E); int day, month, year; day = B - D - X1; month = E < 14 ? E - 1 : E - 13; year = month > 2 ? C - 4716 : C - 4715; try { return new DateTime(year, month, day); } catch { if (badValue == null) throw; return (DateTime)badValue; } } /////////////////////////////////////////////////////////////////////////// /// /// Converts a Julian Day value to a . /// This method was translated from the "computeHMS" function in the /// "date.c" file belonging to the SQLite core library. /// /// /// The Julian Day value to convert. /// /// /// The value to return in the event that the /// Julian Day value is out of the supported range. If this value is /// null, an exception will be thrown instead. /// /// /// A value that contains the hour, minute, and /// second, and millisecond values that are closest to the specified /// Julian Day value. /// private static DateTime computeHMS( long jd, DateTime? badValue ) { if (!isValidJd(jd)) { if (badValue == null) { throw new ArgumentException( "Not a supported Julian Day value."); } return (DateTime)badValue; } int si; si = (int)((jd + 43200000) % 86400000); decimal sd; sd = si / 1000.0M; si = (int)sd; int millisecond = (int)((sd - si) * 1000.0M); sd -= si; int hour; hour = si / 3600; si -= hour * 3600; int minute; minute = si / 60; sd += si - minute * 60; int second = (int)sd; try { DateTime minValue = DateTime.MinValue; return new DateTime( minValue.Year, minValue.Month, minValue.Day, hour, minute, second, millisecond); } catch { if (badValue == null) throw; return (DateTime)badValue; } } /////////////////////////////////////////////////////////////////////////// /// /// Converts a to a Julian Day value. /// This method was translated from the "computeJD" function in /// the "date.c" file belonging to the SQLite core library. /// Since the range of Julian Day values supported by this method /// includes all possible (valid) values of a /// value, it should be extremely difficult for this method to /// raise an exception or return an undefined result. /// /// /// The value to convert. This value /// will be within the range of /// (00:00:00.0000000, January 1, 0001) to /// (23:59:59.9999999, December /// 31, 9999). /// /// /// The nearest Julian Day value corresponding to the specified /// value. /// private static long computeJD( DateTime dateTime ) { int Y, M, D; Y = dateTime.Year; M = dateTime.Month; D = dateTime.Day; if (M <= 2) { Y--; M += 12; } int A, B, X1, X2; A = Y / 100; B = 2 - A + (A / 4); X1 = 36525 * (Y + 4716) / 100; X2 = 306001 * (M + 1) / 10000; long jd; jd = (long)((X1 + X2 + D + B - 1524.5) * 86400000); jd += (dateTime.Hour * 3600000) + (dateTime.Minute * 60000) + (dateTime.Second * 1000) + dateTime.Millisecond; return jd; } #endregion /////////////////////////////////////////////////////////////////////////// /// /// Converts a string into a DateTime, using the DateTimeFormat, DateTimeKind, /// and DateTimeFormatString specified for the connection when it was opened. /// /// /// Acceptable ISO8601 DateTime formats are: /// /// THHmmssK /// THHmmK /// HH:mm:ss.FFFFFFFK /// HH:mm:ssK /// HH:mmK /// yyyy-MM-dd HH:mm:ss.FFFFFFFK /// yyyy-MM-dd HH:mm:ssK /// yyyy-MM-dd HH:mmK /// yyyy-MM-ddTHH:mm:ss.FFFFFFFK /// yyyy-MM-ddTHH:mmK /// yyyy-MM-ddTHH:mm:ssK /// yyyyMMddHHmmssK /// yyyyMMddHHmmK /// yyyyMMddTHHmmssFFFFFFFK /// THHmmss /// THHmm /// HH:mm:ss.FFFFFFF /// HH:mm:ss /// HH:mm /// yyyy-MM-dd HH:mm:ss.FFFFFFF /// yyyy-MM-dd HH:mm:ss /// yyyy-MM-dd HH:mm /// yyyy-MM-ddTHH:mm:ss.FFFFFFF /// yyyy-MM-ddTHH:mm /// yyyy-MM-ddTHH:mm:ss /// yyyyMMddHHmmss /// yyyyMMddHHmm /// yyyyMMddTHHmmssFFFFFFF /// yyyy-MM-dd /// yyyyMMdd /// yy-MM-dd /// /// If the string cannot be matched to one of the above formats -OR- /// the DateTimeFormatString if one was provided, an exception will /// be thrown. /// /// The string containing either a long integer number of 100-nanosecond units since /// System.DateTime.MinValue, a Julian day double, an integer number of seconds since the Unix epoch, a /// culture-independent formatted date and time string, a formatted date and time string in the current /// culture, or an ISO8601-format string. /// A DateTime value public DateTime ToDateTime(string dateText) { return ToDateTime(dateText, _datetimeFormat, _datetimeKind, _datetimeFormatString); } /// /// Converts a string into a DateTime, using the specified DateTimeFormat, /// DateTimeKind and DateTimeFormatString. /// /// /// Acceptable ISO8601 DateTime formats are: /// /// THHmmssK /// THHmmK /// HH:mm:ss.FFFFFFFK /// HH:mm:ssK /// HH:mmK /// yyyy-MM-dd HH:mm:ss.FFFFFFFK /// yyyy-MM-dd HH:mm:ssK /// yyyy-MM-dd HH:mmK /// yyyy-MM-ddTHH:mm:ss.FFFFFFFK /// yyyy-MM-ddTHH:mmK /// yyyy-MM-ddTHH:mm:ssK /// yyyyMMddHHmmssK /// yyyyMMddHHmmK /// yyyyMMddTHHmmssFFFFFFFK /// THHmmss /// THHmm /// HH:mm:ss.FFFFFFF /// HH:mm:ss /// HH:mm /// yyyy-MM-dd HH:mm:ss.FFFFFFF /// yyyy-MM-dd HH:mm:ss /// yyyy-MM-dd HH:mm /// yyyy-MM-ddTHH:mm:ss.FFFFFFF /// yyyy-MM-ddTHH:mm /// yyyy-MM-ddTHH:mm:ss /// yyyyMMddHHmmss /// yyyyMMddHHmm /// yyyyMMddTHHmmssFFFFFFF /// yyyy-MM-dd /// yyyyMMdd /// yy-MM-dd /// /// If the string cannot be matched to one of the above formats -OR- /// the DateTimeFormatString if one was provided, an exception will /// be thrown. /// /// The string containing either a long integer number of 100-nanosecond units since /// System.DateTime.MinValue, a Julian day double, an integer number of seconds since the Unix epoch, a /// culture-independent formatted date and time string, a formatted date and time string in the current /// culture, or an ISO8601-format string. /// The SQLiteDateFormats to use. /// The DateTimeKind to use. /// The DateTime format string to use. /// A DateTime value public static DateTime ToDateTime( string dateText, SQLiteDateFormats format, DateTimeKind kind, string formatString ) { switch (format) { case SQLiteDateFormats.Ticks: { return ToDateTime(Convert.ToInt64( dateText, CultureInfo.InvariantCulture), kind); } case SQLiteDateFormats.JulianDay: { return ToDateTime(Convert.ToDouble( dateText, CultureInfo.InvariantCulture), kind); } case SQLiteDateFormats.UnixEpoch: { return ToDateTime(Convert.ToInt32( dateText, CultureInfo.InvariantCulture), kind); } case SQLiteDateFormats.InvariantCulture: { if (formatString != null) return DateTime.SpecifyKind(DateTime.ParseExact( dateText, formatString, DateTimeFormatInfo.InvariantInfo, kind == DateTimeKind.Utc ? DateTimeStyles.AdjustToUniversal : DateTimeStyles.None), kind); else return DateTime.SpecifyKind(DateTime.Parse( dateText, DateTimeFormatInfo.InvariantInfo, kind == DateTimeKind.Utc ? DateTimeStyles.AdjustToUniversal : DateTimeStyles.None), kind); } case SQLiteDateFormats.CurrentCulture: { if (formatString != null) return DateTime.SpecifyKind(DateTime.ParseExact( dateText, formatString, DateTimeFormatInfo.CurrentInfo, kind == DateTimeKind.Utc ? DateTimeStyles.AdjustToUniversal : DateTimeStyles.None), kind); else return DateTime.SpecifyKind(DateTime.Parse( dateText, DateTimeFormatInfo.CurrentInfo, kind == DateTimeKind.Utc ? DateTimeStyles.AdjustToUniversal : DateTimeStyles.None), kind); } default: /* ISO-8601 */ { if (formatString != null) return DateTime.SpecifyKind(DateTime.ParseExact( dateText, formatString, DateTimeFormatInfo.InvariantInfo, kind == DateTimeKind.Utc ? DateTimeStyles.AdjustToUniversal : DateTimeStyles.None), kind); else return DateTime.SpecifyKind(DateTime.ParseExact( dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, kind == DateTimeKind.Utc ? DateTimeStyles.AdjustToUniversal : DateTimeStyles.None), kind); } } } /// /// Converts a julianday value into a DateTime /// /// The value to convert /// A .NET DateTime public DateTime ToDateTime(double julianDay) { return ToDateTime(julianDay, _datetimeKind); } /// /// Converts a julianday value into a DateTime /// /// The value to convert /// The DateTimeKind to use. /// A .NET DateTime public static DateTime ToDateTime( double julianDay, DateTimeKind kind ) { long jd = DoubleToJd(julianDay); DateTime dateTimeYMD = computeYMD(jd, null); DateTime dateTimeHMS = computeHMS(jd, null); return new DateTime( dateTimeYMD.Year, dateTimeYMD.Month, dateTimeYMD.Day, dateTimeHMS.Hour, dateTimeHMS.Minute, dateTimeHMS.Second, dateTimeHMS.Millisecond, kind); } /// /// Converts the specified number of seconds from the Unix epoch into a /// value. /// /// /// The number of whole seconds since the Unix epoch. /// /// /// Either Utc or Local time. /// /// /// The new value. /// internal static DateTime ToDateTime(int seconds, DateTimeKind kind) { return DateTime.SpecifyKind(UnixEpoch.AddSeconds(seconds), kind); } /// /// Converts the specified number of ticks since the epoch into a /// value. /// /// /// The number of whole ticks since the epoch. /// /// /// Either Utc or Local time. /// /// /// The new value. /// internal static DateTime ToDateTime(long ticks, DateTimeKind kind) { return new DateTime(ticks, kind); } /// /// Converts a DateTime struct to a JulianDay double /// /// The DateTime to convert /// The JulianDay value the Datetime represents public static double ToJulianDay(DateTime value) { return JdToDouble(computeJD(value)); } /// /// Converts a DateTime struct to the whole number of seconds since the /// Unix epoch. /// /// The DateTime to convert /// The whole number of seconds since the Unix epoch public static long ToUnixEpoch(DateTime value) { return (value.Subtract(UnixEpoch).Ticks / TimeSpan.TicksPerSecond); } /// /// Returns the DateTime format string to use for the specified DateTimeKind. /// If is not null, it will be returned verbatim. /// /// The DateTimeKind to use. /// The DateTime format string to use. /// /// The DateTime format string to use for the specified DateTimeKind. /// private static string GetDateTimeKindFormat( DateTimeKind kind, string formatString ) { if (formatString != null) return formatString; return (kind == DateTimeKind.Utc) ? _datetimeFormatUtc : _datetimeFormatLocal; } /// /// Converts a string into a DateTime, using the DateTimeFormat, DateTimeKind, /// and DateTimeFormatString specified for the connection when it was opened. /// /// The DateTime value to convert /// Either a string containing the long integer number of 100-nanosecond units since System.DateTime.MinValue, a /// Julian day double, an integer number of seconds since the Unix epoch, a culture-independent formatted date and time /// string, a formatted date and time string in the current culture, or an ISO8601-format date/time string. public string ToString(DateTime dateValue) { return ToString(dateValue, _datetimeFormat, _datetimeKind, _datetimeFormatString); } /// /// Converts a string into a DateTime, using the DateTimeFormat, DateTimeKind, /// and DateTimeFormatString specified for the connection when it was opened. /// /// The DateTime value to convert /// The SQLiteDateFormats to use. /// The DateTimeKind to use. /// The DateTime format string to use. /// Either a string containing the long integer number of 100-nanosecond units since System.DateTime.MinValue, a /// Julian day double, an integer number of seconds since the Unix epoch, a culture-independent formatted date and time /// string, a formatted date and time string in the current culture, or an ISO8601-format date/time string. public static string ToString( DateTime dateValue, SQLiteDateFormats format, DateTimeKind kind, string formatString ) { switch (format) { case SQLiteDateFormats.Ticks: return dateValue.Ticks.ToString(CultureInfo.InvariantCulture); case SQLiteDateFormats.JulianDay: return ToJulianDay(dateValue).ToString(CultureInfo.InvariantCulture); case SQLiteDateFormats.UnixEpoch: return ((long)(dateValue.Subtract(UnixEpoch).Ticks / TimeSpan.TicksPerSecond)).ToString(); case SQLiteDateFormats.InvariantCulture: return dateValue.ToString((formatString != null) ? formatString : FullFormat, CultureInfo.InvariantCulture); case SQLiteDateFormats.CurrentCulture: return dateValue.ToString((formatString != null) ? formatString : FullFormat, CultureInfo.CurrentCulture); default: return (dateValue.Kind == DateTimeKind.Unspecified) ? DateTime.SpecifyKind(dateValue, kind).ToString( GetDateTimeKindFormat(kind, formatString), CultureInfo.InvariantCulture) : dateValue.ToString( GetDateTimeKindFormat(dateValue.Kind, formatString), CultureInfo.InvariantCulture); } } /// /// Internal function to convert a UTF-8 encoded IntPtr of the specified length to a DateTime. /// /// /// This is a convenience function, which first calls ToString() on the IntPtr to convert it to a string, then calls /// ToDateTime() on the string to return a DateTime. /// /// A pointer to the UTF-8 encoded string /// The length in bytes of the string /// The parsed DateTime value internal DateTime ToDateTime(IntPtr ptr, int len) { return ToDateTime(ToString(ptr, len)); } #endregion /// /// Smart method of splitting a string. Skips quoted elements, removes the quotes. /// /// /// This split function works somewhat like the String.Split() function in that it breaks apart a string into /// pieces and returns the pieces as an array. The primary differences are: /// /// Only one character can be provided as a separator character /// Quoted text inside the string is skipped over when searching for the separator, and the quotes are removed. /// /// Thus, if splitting the following string looking for a comma:
/// One,Two, "Three, Four", Five
///
/// The resulting array would contain
/// [0] One
/// [1] Two
/// [2] Three, Four
/// [3] Five
///
/// Note that the leading and trailing spaces were removed from each item during the split. ///
/// Source string to split apart /// Separator character /// A string array of the split up elements public static string[] Split(string source, char separator) { char[] toks = new char[2] { '\"', separator }; char[] quot = new char[1] { '\"' }; int n = 0; List ls = new List(); string s; while (source.Length > 0) { n = source.IndexOfAny(toks, n); if (n == -1) break; if (source[n] == toks[0]) { //source = source.Remove(n, 1); n = source.IndexOfAny(quot, n + 1); if (n == -1) { //source = "\"" + source; break; } n++; //source = source.Remove(n, 1); } else { s = source.Substring(0, n).Trim(); if (s.Length > 1 && s[0] == quot[0] && s[s.Length - 1] == s[0]) s = s.Substring(1, s.Length - 2); source = source.Substring(n + 1).Trim(); if (s.Length > 0) ls.Add(s); n = 0; } } if (source.Length > 0) { s = source.Trim(); if (s.Length > 1 && s[0] == quot[0] && s[s.Length - 1] == s[0]) s = s.Substring(1, s.Length - 2); ls.Add(s); } string[] ar = new string[ls.Count]; ls.CopyTo(ar, 0); return ar; } /// /// Splits the specified string into multiple strings based on a separator /// and returns the result as an array of strings. /// /// /// The string to split into pieces based on the separator character. If /// this string is null, null will always be returned. If this string is /// empty, an array of zero strings will always be returned. /// /// /// The character used to divide the original string into sub-strings. /// This character cannot be a backslash or a double-quote; otherwise, no /// work will be performed and null will be returned. /// /// /// If this parameter is non-zero, all double-quote characters will be /// retained in the returned list of strings; otherwise, they will be /// dropped. /// /// /// Upon failure, this parameter will be modified to contain an appropriate /// error message. /// /// /// The new array of strings or null if the input string is null -OR- the /// separator character is a backslash or a double-quote -OR- the string /// contains an unbalanced backslash or double-quote character. /// internal static string[] NewSplit( string value, char separator, bool keepQuote, ref string error ) { const char EscapeChar = '\\'; const char QuoteChar = '\"'; // // NOTE: It is illegal for the separator character to be either a // backslash or a double-quote because both of those characters // are used for escaping other characters (e.g. the separator // character). // if ((separator == EscapeChar) || (separator == QuoteChar)) { error = "separator character cannot be the escape or quote characters"; return null; } if (value == null) { error = "string value to split cannot be null"; return null; } int length = value.Length; if (length == 0) return new string[0]; List list = new List(); StringBuilder element = new StringBuilder(); int index = 0; bool escape = false; bool quote = false; while (index < length) { char character = value[index++]; if (escape) { // // HACK: Only consider the escape character to be an actual // "escape" if it is followed by a reserved character; // otherwise, emit the original escape character and // the current character in an effort to help preserve // the original string content. // if ((character != EscapeChar) && (character != QuoteChar) && (character != separator)) { element.Append(EscapeChar); } element.Append(character); escape = false; } else if (character == EscapeChar) { escape = true; } else if (character == QuoteChar) { if (keepQuote) element.Append(character); quote = !quote; } else if (character == separator) { if (quote) { element.Append(character); } else { list.Add(element.ToString()); element.Length = 0; } } else { element.Append(character); } } // // NOTE: An unbalanced escape or quote character in the string is // considered to be a fatal error; therefore, return null. // if (escape || quote) { error = "unbalanced escape or quote character found"; return null; } if (element.Length > 0) list.Add(element.ToString()); return list.ToArray(); } /// /// Queries and returns the string representation for an object, using the /// specified (or current) format provider. /// /// /// The object instance to return the string representation for. /// /// /// The format provider to use -OR- null if the current format provider for /// the thread should be used instead. /// /// /// The string representation for the object instance -OR- null if the /// object instance is also null. /// public static string ToStringWithProvider( object obj, IFormatProvider provider ) { if (obj == null) return null; /* null --> null */ if (obj is string) return (string)obj; /* identity */ IConvertible convertible = obj as IConvertible; if (convertible != null) return convertible.ToString(provider); return obj.ToString(); /* not IConvertible */ } /// /// Attempts to convert an arbitrary object to the Boolean data type. /// Null object values are converted to false. Throws an exception /// upon failure. /// /// /// The object value to convert. /// /// /// The format provider to use. /// /// /// If non-zero, a string value will be converted using the /// /// method; otherwise, the /// method will be used. /// /// /// The converted boolean value. /// internal static bool ToBoolean( object obj, IFormatProvider provider, bool viaFramework ) { if (obj == null) return false; TypeCode typeCode = Type.GetTypeCode(obj.GetType()); switch (typeCode) { case TypeCode.Empty: case TypeCode.DBNull: return false; case TypeCode.Boolean: return (bool)obj; case TypeCode.Char: return ((char)obj) != (char)0 ? true : false; case TypeCode.SByte: return ((sbyte)obj) != (sbyte)0 ? true : false; case TypeCode.Byte: return ((byte)obj) != (byte)0 ? true : false; case TypeCode.Int16: return ((short)obj) != (short)0 ? true : false; case TypeCode.UInt16: return ((ushort)obj) != (ushort)0 ? true : false; case TypeCode.Int32: return ((int)obj) != (int)0 ? true : false; case TypeCode.UInt32: return ((uint)obj) != (uint)0 ? true : false; case TypeCode.Int64: return ((long)obj) != (long)0 ? true : false; case TypeCode.UInt64: return ((ulong)obj) != (ulong)0 ? true : false; case TypeCode.Single: return ((float)obj) != (float)0.0 ? true : false; case TypeCode.Double: return ((double)obj) != (double)0.0 ? true : false; case TypeCode.Decimal: return ((decimal)obj) != Decimal.Zero ? true : false; case TypeCode.String: return viaFramework ? Convert.ToBoolean(obj, provider) : ToBoolean(ToStringWithProvider(obj, provider)); default: throw new SQLiteException(String.Format( CultureInfo.CurrentCulture, "Cannot convert type {0} to boolean", typeCode)); } } /// /// Convert a value to true or false. /// /// A string or number representing true or false /// public static bool ToBoolean(object source) { if (source is bool) return (bool)source; return ToBoolean(ToStringWithProvider( source, CultureInfo.InvariantCulture)); } /// /// Convert a string to true or false. /// /// A string representing true or false /// /// /// "yes", "no", "y", "n", "0", "1", "on", "off" as well as Boolean.FalseString and Boolean.TrueString will all be /// converted to a proper boolean value. /// public static bool ToBoolean(string source) { if (String.Compare(source, bool.TrueString, StringComparison.OrdinalIgnoreCase) == 0) return true; else if (String.Compare(source, bool.FalseString, StringComparison.OrdinalIgnoreCase) == 0) return false; switch(source.ToLower(CultureInfo.InvariantCulture)) { case "yes": case "y": case "1": case "on": return true; case "no": case "n": case "0": case "off": return false; default: throw new ArgumentException("source"); } } #region Type Conversions /// /// Converts a SQLiteType to a .NET Type object /// /// The SQLiteType to convert /// Returns a .NET Type object internal static Type SQLiteTypeToType(SQLiteType t) { if (t.Type == DbType.Object) return _affinitytotype[(int)t.Affinity]; else return SQLiteConvert.DbTypeToType(t.Type); } private static Type[] _affinitytotype = { typeof(object), // Uninitialized (0) typeof(Int64), // Int64 (1) typeof(Double), // Double (2) typeof(string), // Text (3) typeof(byte[]), // Blob (4) typeof(object), // Null (5) typeof(DateTime), // DateTime (10) typeof(object) // None (11) }; /// /// For a given intrinsic type, return a DbType /// /// The native type to convert /// The corresponding (closest match) DbType internal static DbType TypeToDbType(Type typ) { TypeCode tc = Type.GetTypeCode(typ); if (tc == TypeCode.Object) { if (typ == typeof(byte[])) return DbType.Binary; if (typ == typeof(Guid)) return DbType.Guid; return DbType.String; } return _typetodbtype[(int)tc]; } private static DbType[] _typetodbtype = { DbType.Object, // Empty (0) DbType.Binary, // Object (1) DbType.Object, // DBNull (2) DbType.Boolean, // Boolean (3) DbType.SByte, // Char (4) DbType.SByte, // SByte (5) DbType.Byte, // Byte (6) DbType.Int16, // Int16 (7) DbType.UInt16, // UInt16 (8) DbType.Int32, // Int32 (9) DbType.UInt32, // UInt32 (10) DbType.Int64, // Int64 (11) DbType.UInt64, // UInt64 (12) DbType.Single, // Single (13) DbType.Double, // Double (14) DbType.Decimal, // Decimal (15) DbType.DateTime, // DateTime (16) DbType.Object, // ?? (17) DbType.String // String (18) }; /// /// Returns the ColumnSize for the given DbType /// /// The DbType to get the size of /// internal static int DbTypeToColumnSize(DbType typ) { return _dbtypetocolumnsize[(int)typ]; } private static int[] _dbtypetocolumnsize = { int.MaxValue, // AnsiString (0) int.MaxValue, // Binary (1) 1, // Byte (2) 1, // Boolean (3) 8, // Currency (4) 8, // Date (5) 8, // DateTime (6) 8, // Decimal (7) 8, // Double (8) 16, // Guid (9) 2, // Int16 (10) 4, // Int32 (11) 8, // Int64 (12) int.MaxValue, // Object (13) 1, // SByte (14) 4, // Single (15) int.MaxValue, // String (16) 8, // Time (17) 2, // UInt16 (18) 4, // UInt32 (19) 8, // UInt64 (20) 8, // VarNumeric (21) int.MaxValue, // AnsiStringFixedLength (22) int.MaxValue, // StringFixedLength (23) int.MaxValue, // ?? (24) int.MaxValue // Xml (25) }; internal static object DbTypeToNumericPrecision(DbType typ) { return _dbtypetonumericprecision[(int)typ]; } private static object[] _dbtypetonumericprecision = { DBNull.Value, // AnsiString (0) DBNull.Value, // Binary (1) 3, // Byte (2) DBNull.Value, // Boolean (3) 19, // Currency (4) DBNull.Value, // Date (5) DBNull.Value, // DateTime (6) 53, // Decimal (7) 53, // Double (8) DBNull.Value, // Guid (9) 5, // Int16 (10) 10, // Int32 (11) 19, // Int64 (12) DBNull.Value, // Object (13) 3, // SByte (14) 24, // Single (15) DBNull.Value, // String (16) DBNull.Value, // Time (17) 5, // UInt16 (18) 10, // UInt32 (19) 19, // UInt64 (20) 53, // VarNumeric (21) DBNull.Value, // AnsiStringFixedLength (22) DBNull.Value, // StringFixedLength (23) DBNull.Value, // ?? (24) DBNull.Value // Xml (25) }; internal static object DbTypeToNumericScale(DbType typ) { return _dbtypetonumericscale[(int)typ]; } private static object[] _dbtypetonumericscale = { DBNull.Value, // AnsiString (0) DBNull.Value, // Binary (1) 0, // Byte (2) DBNull.Value, // Boolean (3) 4, // Currency (4) DBNull.Value, // Date (5) DBNull.Value, // DateTime (6) DBNull.Value, // Decimal (7) DBNull.Value, // Double (8) DBNull.Value, // Guid (9) 0, // Int16 (10) 0, // Int32 (11) 0, // Int64 (12) DBNull.Value, // Object (13) 0, // SByte (14) DBNull.Value, // Single (15) DBNull.Value, // String (16) DBNull.Value, // Time (17) 0, // UInt16 (18) 0, // UInt32 (19) 0, // UInt64 (20) 0, // VarNumeric (21) DBNull.Value, // AnsiStringFixedLength (22) DBNull.Value, // StringFixedLength (23) DBNull.Value, // ?? (24) DBNull.Value // Xml (25) }; /// /// Determines the default database type name to be used when a /// per-connection value is not available. /// /// /// The connection context for type mappings, if any. /// /// /// The default database type name to use. /// private static string GetDefaultTypeName( SQLiteConnection connection ) { SQLiteConnectionFlags flags = (connection != null) ? connection.Flags : SQLiteConnectionFlags.None; if ((flags & SQLiteConnectionFlags.NoConvertSettings) == SQLiteConnectionFlags.NoConvertSettings) { return FallbackDefaultTypeName; } string name = "Use_SQLiteConvert_DefaultTypeName"; object value = null; string @default = null; if ((connection == null) || !connection.TryGetCachedSetting(name, @default, out value)) { try { value = UnsafeNativeMethods.GetSettingValue(name, @default); if (value == null) value = FallbackDefaultTypeName; } finally { if (connection != null) connection.SetCachedSetting(name, value); } } return SettingValueToString(value); } #if !NET_COMPACT_20 && TRACE_WARNING /// /// If applicable, issues a trace log message warning about falling back to /// the default database type name. /// /// /// The database value type. /// /// /// The flags associated with the parent connection object. /// /// /// The textual name of the database type. /// private static void DefaultTypeNameWarning( DbType dbType, SQLiteConnectionFlags flags, string typeName ) { if ((flags & SQLiteConnectionFlags.TraceWarning) == SQLiteConnectionFlags.TraceWarning) { Trace.WriteLine(String.Format( CultureInfo.CurrentCulture, "WARNING: Type mapping failed, returning default name \"{0}\" for type {1}.", typeName, dbType)); } } /// /// If applicable, issues a trace log message warning about falling back to /// the default database value type. /// /// /// The textual name of the database type. /// /// /// The flags associated with the parent connection object. /// /// /// The database value type. /// private static void DefaultDbTypeWarning( string typeName, SQLiteConnectionFlags flags, DbType? dbType ) { if (!String.IsNullOrEmpty(typeName) && ((flags & SQLiteConnectionFlags.TraceWarning) == SQLiteConnectionFlags.TraceWarning)) { Trace.WriteLine(String.Format( CultureInfo.CurrentCulture, "WARNING: Type mapping failed, returning default type {0} for name \"{1}\".", dbType, typeName)); } } #endif /// /// For a given database value type, return the "closest-match" textual database type name. /// /// The connection context for custom type mappings, if any. /// The database value type. /// The flags associated with the parent connection object. /// The type name or an empty string if it cannot be determined. internal static string DbTypeToTypeName( SQLiteConnection connection, DbType dbType, SQLiteConnectionFlags flags ) { string defaultTypeName = null; if (connection != null) { flags |= connection.Flags; if ((flags & SQLiteConnectionFlags.UseConnectionTypes) == SQLiteConnectionFlags.UseConnectionTypes) { SQLiteDbTypeMap connectionTypeNames = connection._typeNames; if (connectionTypeNames != null) { SQLiteDbTypeMapping value; if (connectionTypeNames.TryGetValue(dbType, out value)) return value.typeName; } } // // NOTE: Use the default database type name for the connection. // defaultTypeName = connection.DefaultTypeName; } if ((flags & SQLiteConnectionFlags.NoGlobalTypes) == SQLiteConnectionFlags.NoGlobalTypes) { if (defaultTypeName != null) return defaultTypeName; defaultTypeName = GetDefaultTypeName(connection); #if !NET_COMPACT_20 && TRACE_WARNING DefaultTypeNameWarning(dbType, flags, defaultTypeName); #endif return defaultTypeName; } lock (_syncRoot) { if (_typeNames == null) _typeNames = GetSQLiteDbTypeMap(); SQLiteDbTypeMapping value; if (_typeNames.TryGetValue(dbType, out value)) return value.typeName; } if (defaultTypeName != null) return defaultTypeName; defaultTypeName = GetDefaultTypeName(connection); #if !NET_COMPACT_20 && TRACE_WARNING DefaultTypeNameWarning(dbType, flags, defaultTypeName); #endif return defaultTypeName; } /// /// Convert a DbType to a Type /// /// The DbType to convert from /// The closest-match .NET type internal static Type DbTypeToType(DbType typ) { return _dbtypeToType[(int)typ]; } private static Type[] _dbtypeToType = { typeof(string), // AnsiString (0) typeof(byte[]), // Binary (1) typeof(byte), // Byte (2) typeof(bool), // Boolean (3) typeof(decimal), // Currency (4) typeof(DateTime), // Date (5) typeof(DateTime), // DateTime (6) typeof(decimal), // Decimal (7) typeof(double), // Double (8) typeof(Guid), // Guid (9) typeof(Int16), // Int16 (10) typeof(Int32), // Int32 (11) typeof(Int64), // Int64 (12) typeof(object), // Object (13) typeof(sbyte), // SByte (14) typeof(float), // Single (15) typeof(string), // String (16) typeof(DateTime), // Time (17) typeof(UInt16), // UInt16 (18) typeof(UInt32), // UInt32 (19) typeof(UInt64), // UInt64 (20) typeof(double), // VarNumeric (21) typeof(string), // AnsiStringFixedLength (22) typeof(string), // StringFixedLength (23) typeof(string), // ?? (24) typeof(string), // Xml (25) }; /// /// For a given type, return the closest-match SQLite TypeAffinity, which only understands a very limited subset of types. /// /// The type to evaluate /// The SQLite type affinity for that type. internal static TypeAffinity TypeToAffinity(Type typ) { TypeCode tc = Type.GetTypeCode(typ); if (tc == TypeCode.Object) { if (typ == typeof(byte[]) || typ == typeof(Guid)) return TypeAffinity.Blob; else return TypeAffinity.Text; } return _typecodeAffinities[(int)tc]; } private static TypeAffinity[] _typecodeAffinities = { TypeAffinity.Null, // Empty (0) TypeAffinity.Blob, // Object (1) TypeAffinity.Null, // DBNull (2) TypeAffinity.Int64, // Boolean (3) TypeAffinity.Int64, // Char (4) TypeAffinity.Int64, // SByte (5) TypeAffinity.Int64, // Byte (6) TypeAffinity.Int64, // Int16 (7) TypeAffinity.Int64, // UInt16 (8) TypeAffinity.Int64, // Int32 (9) TypeAffinity.Int64, // UInt32 (10) TypeAffinity.Int64, // Int64 (11) TypeAffinity.Int64, // UInt64 (12) TypeAffinity.Double, // Single (13) TypeAffinity.Double, // Double (14) TypeAffinity.Double, // Decimal (15) TypeAffinity.DateTime, // DateTime (16) TypeAffinity.Null, // ?? (17) TypeAffinity.Text // String (18) }; /// /// Builds and returns a map containing the database column types /// recognized by this provider. /// /// /// A map containing the database column types recognized by this /// provider. /// private static SQLiteDbTypeMap GetSQLiteDbTypeMap() { return new SQLiteDbTypeMap(new SQLiteDbTypeMapping[] { new SQLiteDbTypeMapping("BIGINT", DbType.Int64, false), new SQLiteDbTypeMapping("BIGUINT", DbType.UInt64, false), new SQLiteDbTypeMapping("BINARY", DbType.Binary, false), new SQLiteDbTypeMapping("BIT", DbType.Boolean, true), new SQLiteDbTypeMapping("BLOB", DbType.Binary, true), new SQLiteDbTypeMapping("BOOL", DbType.Boolean, false), new SQLiteDbTypeMapping("BOOLEAN", DbType.Boolean, false), new SQLiteDbTypeMapping("CHAR", DbType.AnsiStringFixedLength, true), new SQLiteDbTypeMapping("CLOB", DbType.String, false), new SQLiteDbTypeMapping("COUNTER", DbType.Int64, false), new SQLiteDbTypeMapping("CURRENCY", DbType.Decimal, false), new SQLiteDbTypeMapping("DATE", DbType.DateTime, false), new SQLiteDbTypeMapping("DATETIME", DbType.DateTime, true), new SQLiteDbTypeMapping("DECIMAL", DbType.Decimal, true), new SQLiteDbTypeMapping("DOUBLE", DbType.Double, false), new SQLiteDbTypeMapping("FLOAT", DbType.Double, false), new SQLiteDbTypeMapping("GENERAL", DbType.Binary, false), new SQLiteDbTypeMapping("GUID", DbType.Guid, false), new SQLiteDbTypeMapping("IDENTITY", DbType.Int64, false), new SQLiteDbTypeMapping("IMAGE", DbType.Binary, false), new SQLiteDbTypeMapping("INT", DbType.Int32, true), new SQLiteDbTypeMapping("INT8", DbType.SByte, false), new SQLiteDbTypeMapping("INT16", DbType.Int16, false), new SQLiteDbTypeMapping("INT32", DbType.Int32, false), new SQLiteDbTypeMapping("INT64", DbType.Int64, false), new SQLiteDbTypeMapping("INTEGER", DbType.Int64, true), new SQLiteDbTypeMapping("INTEGER8", DbType.SByte, false), new SQLiteDbTypeMapping("INTEGER16", DbType.Int16, false), new SQLiteDbTypeMapping("INTEGER32", DbType.Int32, false), new SQLiteDbTypeMapping("INTEGER64", DbType.Int64, false), new SQLiteDbTypeMapping("LOGICAL", DbType.Boolean, false), new SQLiteDbTypeMapping("LONG", DbType.Int64, false), new SQLiteDbTypeMapping("LONGCHAR", DbType.String, false), new SQLiteDbTypeMapping("LONGTEXT", DbType.String, false), new SQLiteDbTypeMapping("LONGVARCHAR", DbType.String, false), new SQLiteDbTypeMapping("MEMO", DbType.String, false), new SQLiteDbTypeMapping("MONEY", DbType.Decimal, false), new SQLiteDbTypeMapping("NCHAR", DbType.StringFixedLength, true), new SQLiteDbTypeMapping("NOTE", DbType.String, false), new SQLiteDbTypeMapping("NTEXT", DbType.String, false), new SQLiteDbTypeMapping("NUMBER", DbType.Decimal, false), new SQLiteDbTypeMapping("NUMERIC", DbType.Decimal, false), new SQLiteDbTypeMapping("NVARCHAR", DbType.String, true), new SQLiteDbTypeMapping("OLEOBJECT", DbType.Binary, false), new SQLiteDbTypeMapping("RAW", DbType.Binary, false), new SQLiteDbTypeMapping("REAL", DbType.Double, true), new SQLiteDbTypeMapping("SINGLE", DbType.Single, true), new SQLiteDbTypeMapping("SMALLDATE", DbType.DateTime, false), new SQLiteDbTypeMapping("SMALLINT", DbType.Int16, true), new SQLiteDbTypeMapping("SMALLUINT", DbType.UInt16, true), new SQLiteDbTypeMapping("STRING", DbType.String, false), new SQLiteDbTypeMapping("TEXT", DbType.String, false), new SQLiteDbTypeMapping("TIME", DbType.DateTime, false), new SQLiteDbTypeMapping("TIMESTAMP", DbType.DateTime, false), new SQLiteDbTypeMapping("TINYINT", DbType.Byte, true), new SQLiteDbTypeMapping("TINYSINT", DbType.SByte, true), new SQLiteDbTypeMapping("UINT", DbType.UInt32, true), new SQLiteDbTypeMapping("UINT8", DbType.Byte, false), new SQLiteDbTypeMapping("UINT16", DbType.UInt16, false), new SQLiteDbTypeMapping("UINT32", DbType.UInt32, false), new SQLiteDbTypeMapping("UINT64", DbType.UInt64, false), new SQLiteDbTypeMapping("ULONG", DbType.UInt64, false), new SQLiteDbTypeMapping("UNIQUEIDENTIFIER", DbType.Guid, true), new SQLiteDbTypeMapping("UNSIGNEDINTEGER", DbType.UInt64, true), new SQLiteDbTypeMapping("UNSIGNEDINTEGER8", DbType.Byte, false), new SQLiteDbTypeMapping("UNSIGNEDINTEGER16", DbType.UInt16, false), new SQLiteDbTypeMapping("UNSIGNEDINTEGER32", DbType.UInt32, false), new SQLiteDbTypeMapping("UNSIGNEDINTEGER64", DbType.UInt64, false), new SQLiteDbTypeMapping("VARBINARY", DbType.Binary, false), new SQLiteDbTypeMapping("VARCHAR", DbType.AnsiString, true), new SQLiteDbTypeMapping("VARCHAR2", DbType.AnsiString, false), new SQLiteDbTypeMapping("YESNO", DbType.Boolean, false) }); } /// /// Determines if a database type is considered to be a string. /// /// /// The database type to check. /// /// /// Non-zero if the database type is considered to be a string, zero /// otherwise. /// internal static bool IsStringDbType( DbType type ) { switch (type) { case DbType.AnsiString: case DbType.String: case DbType.AnsiStringFixedLength: case DbType.StringFixedLength: return true; default: return false; } } /// /// Determines and returns the runtime configuration setting string that /// should be used in place of the specified object value. /// /// /// The object value to convert to a string. /// /// /// Either the string to use in place of the object value -OR- null if it /// cannot be determined. /// private static string SettingValueToString( object value ) { if (value is string) return (string)value; if (value != null) return value.ToString(); return null; } /// /// Determines the default value to be used when a /// per-connection value is not available. /// /// /// The connection context for type mappings, if any. /// /// /// The default value to use. /// private static DbType GetDefaultDbType( SQLiteConnection connection ) { SQLiteConnectionFlags flags = (connection != null) ? connection.Flags : SQLiteConnectionFlags.None; if ((flags & SQLiteConnectionFlags.NoConvertSettings) == SQLiteConnectionFlags.NoConvertSettings) { return FallbackDefaultDbType; } bool found = false; string name = "Use_SQLiteConvert_DefaultDbType"; object value = null; string @default = null; if ((connection == null) || !connection.TryGetCachedSetting(name, @default, out value)) { value = UnsafeNativeMethods.GetSettingValue(name, @default); if (value == null) value = FallbackDefaultDbType; } else { found = true; } try { if (!(value is DbType)) { value = SQLiteConnection.TryParseEnum( typeof(DbType), SettingValueToString(value), true); if (!(value is DbType)) value = FallbackDefaultDbType; } return (DbType)value; } finally { if (!found && (connection != null)) connection.SetCachedSetting(name, value); } } /// /// Determines if the specified textual value appears to be a /// value. /// /// /// The textual value to inspect. /// /// /// Non-zero if the text looks like a value, /// zero otherwise. /// internal static bool LooksLikeNull( string text ) { return (text == null); } /// /// Determines if the specified textual value appears to be an /// value. /// /// /// The textual value to inspect. /// /// /// Non-zero if the text looks like an value, /// zero otherwise. /// internal static bool LooksLikeInt64( string text ) { long longValue; #if !PLATFORM_COMPACTFRAMEWORK if (!long.TryParse( text, NumberStyles.Integer, CultureInfo.InvariantCulture, out longValue)) { return false; } #else try { longValue = long.Parse( text, NumberStyles.Integer, CultureInfo.InvariantCulture); } catch { return false; } #endif return String.Equals( longValue.ToString(CultureInfo.InvariantCulture), text, StringComparison.Ordinal); } /// /// Determines if the specified textual value appears to be a /// value. /// /// /// The textual value to inspect. /// /// /// Non-zero if the text looks like a value, /// zero otherwise. /// internal static bool LooksLikeDouble( string text ) { double doubleValue; #if !PLATFORM_COMPACTFRAMEWORK if (!double.TryParse( text, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out doubleValue)) { return false; } #else try { doubleValue = double.Parse(text, CultureInfo.InvariantCulture); } catch { return false; } #endif return String.Equals( doubleValue.ToString(CultureInfo.InvariantCulture), text, StringComparison.Ordinal); } /// /// Determines if the specified textual value appears to be a /// value. /// /// /// The object instance configured with /// the chosen format. /// /// /// The textual value to inspect. /// /// /// Non-zero if the text looks like a in the /// configured format, zero otherwise. /// internal static bool LooksLikeDateTime( SQLiteConvert convert, string text ) { if (convert == null) return false; try { DateTime dateTimeValue = convert.ToDateTime(text); if (String.Equals( convert.ToString(dateTimeValue), text, StringComparison.Ordinal)) { return true; } } catch { // do nothing. } return false; } /// /// For a given textual database type name, return the "closest-match" database type. /// This method is called during query result processing; therefore, its performance /// is critical. /// /// The connection context for custom type mappings, if any. /// The textual name of the database type to match. /// The flags associated with the parent connection object. /// The .NET DBType the text evaluates to. internal static DbType TypeNameToDbType( SQLiteConnection connection, string typeName, SQLiteConnectionFlags flags ) { DbType? defaultDbType = null; if (connection != null) { flags |= connection.Flags; if ((flags & SQLiteConnectionFlags.UseConnectionTypes) == SQLiteConnectionFlags.UseConnectionTypes) { SQLiteDbTypeMap connectionTypeNames = connection._typeNames; if (connectionTypeNames != null) { if (typeName != null) { SQLiteDbTypeMapping value; if (connectionTypeNames.TryGetValue(typeName, out value)) { return value.dataType; } else { int index = typeName.IndexOf('('); if ((index > 0) && connectionTypeNames.TryGetValue(typeName.Substring(0, index).TrimEnd(), out value)) { return value.dataType; } } } } } // // NOTE: Use the default database type for the connection. // defaultDbType = connection.DefaultDbType; } if ((flags & SQLiteConnectionFlags.NoGlobalTypes) == SQLiteConnectionFlags.NoGlobalTypes) { if (defaultDbType != null) return (DbType)defaultDbType; defaultDbType = GetDefaultDbType(connection); #if !NET_COMPACT_20 && TRACE_WARNING DefaultDbTypeWarning(typeName, flags, defaultDbType); #endif return (DbType)defaultDbType; } lock (_syncRoot) { if (_typeNames == null) _typeNames = GetSQLiteDbTypeMap(); if (typeName != null) { SQLiteDbTypeMapping value; if (_typeNames.TryGetValue(typeName, out value)) { return value.dataType; } else { int index = typeName.IndexOf('('); if ((index > 0) && _typeNames.TryGetValue(typeName.Substring(0, index).TrimEnd(), out value)) { return value.dataType; } } } } if (defaultDbType != null) return (DbType)defaultDbType; defaultDbType = GetDefaultDbType(connection); #if !NET_COMPACT_20 && TRACE_WARNING DefaultDbTypeWarning(typeName, flags, defaultDbType); #endif return (DbType)defaultDbType; } #endregion private static object _syncRoot = new object(); private static SQLiteDbTypeMap _typeNames = null; } /// /// SQLite has very limited types, and is inherently text-based. The first 5 types below represent the sum of all types SQLite /// understands. The DateTime extension to the spec is for internal use only. /// public enum TypeAffinity { /// /// Not used /// Uninitialized = 0, /// /// All integers in SQLite default to Int64 /// Int64 = 1, /// /// All floating point numbers in SQLite default to double /// Double = 2, /// /// The default data type of SQLite is text /// Text = 3, /// /// Typically blob types are only seen when returned from a function /// Blob = 4, /// /// Null types can be returned from functions /// Null = 5, /// /// Used internally by this provider /// DateTime = 10, /// /// Used internally by this provider /// None = 11, } /// /// These are the event types associated with the /// /// delegate (and its corresponding event) and the /// class. /// public enum SQLiteConnectionEventType { /// /// Not used. /// Invalid = -1, /// /// Not used. /// Unknown = 0, /// /// The connection is being opened. /// Opening = 1, /// /// The connection string has been parsed. /// ConnectionString = 2, /// /// The connection was opened. /// Opened = 3, /// /// The method was called on the /// connection. /// ChangeDatabase = 4, /// /// A transaction was created using the connection. /// NewTransaction = 5, /// /// The connection was enlisted into a transaction. /// EnlistTransaction = 6, /// /// A command was created using the connection. /// NewCommand = 7, /// /// A data reader was created using the connection. /// NewDataReader = 8, /// /// An instance of a derived class has /// been created to wrap a native resource. /// NewCriticalHandle = 9, /// /// The connection is being closed. /// Closing = 10, /// /// The connection was closed. /// Closed = 11, /// /// A command is being disposed. /// DisposingCommand = 12, /// /// A data reader is being disposed. /// DisposingDataReader = 13, /// /// A data reader is being closed. /// ClosingDataReader = 14 } /// /// This implementation of SQLite for ADO.NET can process date/time fields in /// databases in one of six formats. /// /// /// ISO8601 format is more compatible, readable, fully-processable, but less /// accurate as it does not provide time down to fractions of a second. /// JulianDay is the numeric format the SQLite uses internally and is arguably /// the most compatible with 3rd party tools. It is not readable as text /// without post-processing. Ticks less compatible with 3rd party tools that /// query the database, and renders the DateTime field unreadable as text /// without post-processing. UnixEpoch is more compatible with Unix systems. /// InvariantCulture allows the configured format for the invariant culture /// format to be used and is human readable. CurrentCulture allows the /// configured format for the current culture to be used and is also human /// readable. /// /// The preferred order of choosing a DateTime format is JulianDay, ISO8601, /// and then Ticks. Ticks is mainly present for legacy code support. /// public enum SQLiteDateFormats { /// /// Use the value of DateTime.Ticks. This value is not recommended and is not well supported with LINQ. /// Ticks = 0, /// /// Use the ISO-8601 format. Uses the "yyyy-MM-dd HH:mm:ss.FFFFFFFK" format for UTC DateTime values and /// "yyyy-MM-dd HH:mm:ss.FFFFFFF" format for local DateTime values). /// ISO8601 = 1, /// /// The interval of time in days and fractions of a day since January 1, 4713 BC. /// JulianDay = 2, /// /// The whole number of seconds since the Unix epoch (January 1, 1970). /// UnixEpoch = 3, /// /// Any culture-independent string value that the .NET Framework can interpret as a valid DateTime. /// InvariantCulture = 4, /// /// Any string value that the .NET Framework can interpret as a valid DateTime using the current culture. /// CurrentCulture = 5, /// /// The default format for this provider. /// Default = ISO8601 } /// /// This enum determines how SQLite treats its journal file. /// /// /// By default SQLite will create and delete the journal file when needed during a transaction. /// However, for some computers running certain filesystem monitoring tools, the rapid /// creation and deletion of the journal file can cause those programs to fail, or to interfere with SQLite. /// /// If a program or virus scanner is interfering with SQLite's journal file, you may receive errors like "unable to open database file" /// when starting a transaction. If this is happening, you may want to change the default journal mode to Persist. /// public enum SQLiteJournalModeEnum { /// /// The default mode, this causes SQLite to use the existing journaling mode for the database. /// Default = -1, /// /// SQLite will create and destroy the journal file as-needed. /// Delete = 0, /// /// When this is set, SQLite will keep the journal file even after a transaction has completed. It's contents will be erased, /// and the journal re-used as often as needed. If it is deleted, it will be recreated the next time it is needed. /// Persist = 1, /// /// This option disables the rollback journal entirely. Interrupted transactions or a program crash can cause database /// corruption in this mode! /// Off = 2, /// /// SQLite will truncate the journal file to zero-length instead of deleting it. /// Truncate = 3, /// /// SQLite will store the journal in volatile RAM. This saves disk I/O but at the expense of database safety and integrity. /// If the application using SQLite crashes in the middle of a transaction when the MEMORY journaling mode is set, then the /// database file will very likely go corrupt. /// Memory = 4, /// /// SQLite uses a write-ahead log instead of a rollback journal to implement transactions. The WAL journaling mode is persistent; /// after being set it stays in effect across multiple database connections and after closing and reopening the database. A database /// in WAL journaling mode can only be accessed by SQLite version 3.7.0 or later. /// Wal = 5 } /// /// Possible values for the "synchronous" database setting. This setting determines /// how often the database engine calls the xSync method of the VFS. /// internal enum SQLiteSynchronousEnum { /// /// Use the default "synchronous" database setting. Currently, this should be /// the same as using the FULL mode. /// Default = -1, /// /// The database engine continues without syncing as soon as it has handed /// data off to the operating system. If the application running SQLite /// crashes, the data will be safe, but the database might become corrupted /// if the operating system crashes or the computer loses power before that /// data has been written to the disk surface. /// Off = 0, /// /// The database engine will still sync at the most critical moments, but /// less often than in FULL mode. There is a very small (though non-zero) /// chance that a power failure at just the wrong time could corrupt the /// database in NORMAL mode. /// Normal = 1, /// /// The database engine will use the xSync method of the VFS to ensure that /// all content is safely written to the disk surface prior to continuing. /// This ensures that an operating system crash or power failure will not /// corrupt the database. FULL synchronous is very safe, but it is also /// slower. /// Full = 2 } /// /// The requested command execution type. This controls which method of the /// object will be called. /// public enum SQLiteExecuteType { /// /// Do nothing. No method will be called. /// None = 0, /// /// The command is not expected to return a result -OR- the result is not /// needed. The or /// method /// will be called. /// NonQuery = 1, /// /// The command is expected to return a scalar result -OR- the result should /// be limited to a scalar result. The /// or method will /// be called. /// Scalar = 2, /// /// The command is expected to return result. /// The or /// method will /// be called. /// Reader = 3, /// /// Use the default command execution type. Using this value is the same /// as using the value. /// Default = NonQuery /* TODO: Good default? */ } /// /// The action code responsible for the current call into the authorizer. /// public enum SQLiteAuthorizerActionCode { /// /// No action is being performed. This value should not be used from /// external code. /// None = -1, /// /// No longer used. /// Copy = 0, /// /// An index will be created. The action-specific arguments are the /// index name and the table name. /// /// CreateIndex = 1, /// /// A table will be created. The action-specific arguments are the /// table name and a null value. /// CreateTable = 2, /// /// A temporary index will be created. The action-specific arguments /// are the index name and the table name. /// CreateTempIndex = 3, /// /// A temporary table will be created. The action-specific arguments /// are the table name and a null value. /// CreateTempTable = 4, /// /// A temporary trigger will be created. The action-specific arguments /// are the trigger name and the table name. /// CreateTempTrigger = 5, /// /// A temporary view will be created. The action-specific arguments are /// the view name and a null value. /// CreateTempView = 6, /// /// A trigger will be created. The action-specific arguments are the /// trigger name and the table name. /// CreateTrigger = 7, /// /// A view will be created. The action-specific arguments are the view /// name and a null value. /// CreateView = 8, /// /// A DELETE statement will be executed. The action-specific arguments /// are the table name and a null value. /// Delete = 9, /// /// An index will be dropped. The action-specific arguments are the /// index name and the table name. /// DropIndex = 10, /// /// A table will be dropped. The action-specific arguments are the tables /// name and a null value. /// DropTable = 11, /// /// A temporary index will be dropped. The action-specific arguments are /// the index name and the table name. /// DropTempIndex = 12, /// /// A temporary table will be dropped. The action-specific arguments are /// the table name and a null value. /// DropTempTable = 13, /// /// A temporary trigger will be dropped. The action-specific arguments /// are the trigger name and the table name. /// DropTempTrigger = 14, /// /// A temporary view will be dropped. The action-specific arguments are /// the view name and a null value. /// DropTempView = 15, /// /// A trigger will be dropped. The action-specific arguments are the /// trigger name and the table name. /// DropTrigger = 16, /// /// A view will be dropped. The action-specific arguments are the view /// name and a null value. /// DropView = 17, /// /// An INSERT statement will be executed. The action-specific arguments /// are the table name and a null value. /// Insert = 18, /// /// A PRAGMA statement will be executed. The action-specific arguments /// are the name of the PRAGMA and the new value or a null value. /// Pragma = 19, /// /// A table column will be read. The action-specific arguments are the /// table name and the column name. /// Read = 20, /// /// A SELECT statement will be executed. The action-specific arguments /// are both null values. /// Select = 21, /// /// A transaction will be started, committed, or rolled back. The /// action-specific arguments are the name of the operation (BEGIN, /// COMMIT, or ROLLBACK) and a null value. /// Transaction = 22, /// /// An UPDATE statement will be executed. The action-specific arguments /// are the table name and the column name. /// Update = 23, /// /// A database will be attached to the connection. The action-specific /// arguments are the database file name and a null value. /// Attach = 24, /// /// A database will be detached from the connection. The action-specific /// arguments are the database name and a null value. /// Detach = 25, /// /// The schema of a table will be altered. The action-specific arguments /// are the database name and the table name. /// AlterTable = 26, /// /// An index will be deleted and then recreated. The action-specific /// arguments are the index name and a null value. /// Reindex = 27, /// /// A table will be analyzed to gathers statistics about it. The /// action-specific arguments are the table name and a null value. /// Analyze = 28, /// /// A virtual table will be created. The action-specific arguments are /// the table name and the module name. /// CreateVtable = 29, /// /// A virtual table will be dropped. The action-specific arguments are /// the table name and the module name. /// DropVtable = 30, /// /// A SQL function will be called. The action-specific arguments are a /// null value and the function name. /// Function = 31, /// /// A savepoint will be created, released, or rolled back. The /// action-specific arguments are the name of the operation (BEGIN, /// RELEASE, or ROLLBACK) and the savepoint name. /// Savepoint = 32, /// /// A recursive query will be executed. The action-specific arguments /// are two null values. /// Recursive = 33 } /// /// The return code for the current call into the authorizer. /// public enum SQLiteAuthorizerReturnCode { /// /// The action will be allowed. /// Ok = 0, /// /// The overall action will be disallowed and an error message will be /// returned from the query preparation method. /// Deny = 1, /// /// The specific action will be disallowed; however, the overall action /// will continue. The exact effects of this return code vary depending /// on the specific action, please refer to the SQLite core library /// documentation for futher details. /// Ignore = 2 } /// /// Class used internally to determine the datatype of a column in a resultset /// internal sealed class SQLiteType { /// /// The DbType of the column, or DbType.Object if it cannot be determined /// internal DbType Type; /// /// The affinity of a column, used for expressions or when Type is DbType.Object /// internal TypeAffinity Affinity; /////////////////////////////////////////////////////////////////////////// /// /// Constructs a default instance of this type. /// public SQLiteType() { // do nothing. } /////////////////////////////////////////////////////////////////////////// /// /// Constructs an instance of this type with the specified field values. /// /// /// The type affinity to use for the new instance. /// /// /// The database type to use for the new instance. /// public SQLiteType( TypeAffinity affinity, DbType type ) : this() { this.Affinity = affinity; this.Type = type; } } ///////////////////////////////////////////////////////////////////////////// internal sealed class SQLiteDbTypeMap : Dictionary { #region Private Data private Dictionary reverse; #endregion ///////////////////////////////////////////////////////////////////////// #region Public Constructors public SQLiteDbTypeMap() : base(new TypeNameStringComparer()) { reverse = new Dictionary(); } ///////////////////////////////////////////////////////////////////////// public SQLiteDbTypeMap( IEnumerable collection ) : this() { Add(collection); } #endregion ///////////////////////////////////////////////////////////////////////// #region System.Collections.Generic.Dictionary "Overrides" public new int Clear() { int result = 0; if (reverse != null) { result += reverse.Count; reverse.Clear(); } result += base.Count; base.Clear(); return result; } #endregion ///////////////////////////////////////////////////////////////////////// #region SQLiteDbTypeMapping Helper Methods public void Add( IEnumerable collection ) { if (collection == null) throw new ArgumentNullException("collection"); foreach (SQLiteDbTypeMapping item in collection) Add(item); } ///////////////////////////////////////////////////////////////////////// public void Add(SQLiteDbTypeMapping item) { if (item == null) throw new ArgumentNullException("item"); if (item.typeName == null) throw new ArgumentException("item type name cannot be null"); base.Add(item.typeName, item); if (item.primary) reverse.Add(item.dataType, item); } #endregion ///////////////////////////////////////////////////////////////////////// #region DbType Helper Methods public bool ContainsKey(DbType key) { if (reverse == null) return false; return reverse.ContainsKey(key); } ///////////////////////////////////////////////////////////////////////// public bool TryGetValue(DbType key, out SQLiteDbTypeMapping value) { if (reverse == null) { value = null; return false; } return reverse.TryGetValue(key, out value); } ///////////////////////////////////////////////////////////////////////// public bool Remove(DbType key) { if (reverse == null) return false; return reverse.Remove(key); } #endregion } ///////////////////////////////////////////////////////////////////////////// internal sealed class SQLiteDbTypeMapping { internal SQLiteDbTypeMapping( string newTypeName, DbType newDataType, bool newPrimary ) { typeName = newTypeName; dataType = newDataType; primary = newPrimary; } internal string typeName; internal DbType dataType; internal bool primary; } internal sealed class TypeNameStringComparer : IEqualityComparer { #region IEqualityComparer Members public bool Equals( string left, string right ) { return String.Equals(left, right, StringComparison.OrdinalIgnoreCase); } /////////////////////////////////////////////////////////////////////////// public int GetHashCode( string value ) { // // NOTE: The only thing that we must guarantee here, according // to the MSDN documentation for IEqualityComparer, is // that for two given strings, if Equals return true then // the two strings must hash to the same value. // if (value != null) #if !PLATFORM_COMPACTFRAMEWORK return value.ToLowerInvariant().GetHashCode(); #else return value.ToLower().GetHashCode(); #endif else throw new ArgumentNullException("value"); } #endregion } }