|  |  | 1 |  | #pragma warning disable CS1591 | 
|  |  | 2 |  |  | 
|  |  | 3 |  | using System; | 
|  |  | 4 |  | using System.Collections.Generic; | 
|  |  | 5 |  | using System.Data; | 
|  |  | 6 |  | using System.Globalization; | 
|  |  | 7 |  | using Microsoft.Data.Sqlite; | 
|  |  | 8 |  |  | 
|  |  | 9 |  | namespace Emby.Server.Implementations.Data | 
|  |  | 10 |  | { | 
|  |  | 11 |  |     public static class SqliteExtensions | 
|  |  | 12 |  |     { | 
|  |  | 13 |  |         private const string DatetimeFormatUtc = "yyyy-MM-dd HH:mm:ss.FFFFFFFK"; | 
|  |  | 14 |  |         private const string DatetimeFormatLocal = "yyyy-MM-dd HH:mm:ss.FFFFFFF"; | 
|  |  | 15 |  |  | 
|  |  | 16 |  |         /// <summary> | 
|  |  | 17 |  |         /// An array of ISO-8601 DateTime formats that we support parsing. | 
|  |  | 18 |  |         /// </summary> | 
|  | 0 | 19 |  |         private static readonly string[] _datetimeFormats = new string[] | 
|  | 0 | 20 |  |         { | 
|  | 0 | 21 |  |             "THHmmssK", | 
|  | 0 | 22 |  |             "THHmmK", | 
|  | 0 | 23 |  |             "HH:mm:ss.FFFFFFFK", | 
|  | 0 | 24 |  |             "HH:mm:ssK", | 
|  | 0 | 25 |  |             "HH:mmK", | 
|  | 0 | 26 |  |             DatetimeFormatUtc, | 
|  | 0 | 27 |  |             "yyyy-MM-dd HH:mm:ssK", | 
|  | 0 | 28 |  |             "yyyy-MM-dd HH:mmK", | 
|  | 0 | 29 |  |             "yyyy-MM-ddTHH:mm:ss.FFFFFFFK", | 
|  | 0 | 30 |  |             "yyyy-MM-ddTHH:mmK", | 
|  | 0 | 31 |  |             "yyyy-MM-ddTHH:mm:ssK", | 
|  | 0 | 32 |  |             "yyyyMMddHHmmssK", | 
|  | 0 | 33 |  |             "yyyyMMddHHmmK", | 
|  | 0 | 34 |  |             "yyyyMMddTHHmmssFFFFFFFK", | 
|  | 0 | 35 |  |             "THHmmss", | 
|  | 0 | 36 |  |             "THHmm", | 
|  | 0 | 37 |  |             "HH:mm:ss.FFFFFFF", | 
|  | 0 | 38 |  |             "HH:mm:ss", | 
|  | 0 | 39 |  |             "HH:mm", | 
|  | 0 | 40 |  |             DatetimeFormatLocal, | 
|  | 0 | 41 |  |             "yyyy-MM-dd HH:mm:ss", | 
|  | 0 | 42 |  |             "yyyy-MM-dd HH:mm", | 
|  | 0 | 43 |  |             "yyyy-MM-ddTHH:mm:ss.FFFFFFF", | 
|  | 0 | 44 |  |             "yyyy-MM-ddTHH:mm", | 
|  | 0 | 45 |  |             "yyyy-MM-ddTHH:mm:ss", | 
|  | 0 | 46 |  |             "yyyyMMddHHmmss", | 
|  | 0 | 47 |  |             "yyyyMMddHHmm", | 
|  | 0 | 48 |  |             "yyyyMMddTHHmmssFFFFFFF", | 
|  | 0 | 49 |  |             "yyyy-MM-dd", | 
|  | 0 | 50 |  |             "yyyyMMdd", | 
|  | 0 | 51 |  |             "yy-MM-dd" | 
|  | 0 | 52 |  |         }; | 
|  |  | 53 |  |  | 
|  |  | 54 |  |         public static IEnumerable<SqliteDataReader> Query(this SqliteConnection sqliteConnection, string commandText) | 
|  |  | 55 |  |         { | 
|  |  | 56 |  |             if (sqliteConnection.State != ConnectionState.Open) | 
|  |  | 57 |  |             { | 
|  |  | 58 |  |                 sqliteConnection.Open(); | 
|  |  | 59 |  |             } | 
|  |  | 60 |  |  | 
|  |  | 61 |  |             using var command = sqliteConnection.CreateCommand(); | 
|  |  | 62 |  |             command.CommandText = commandText; | 
|  |  | 63 |  |             using (var reader = command.ExecuteReader()) | 
|  |  | 64 |  |             { | 
|  |  | 65 |  |                 while (reader.Read()) | 
|  |  | 66 |  |                 { | 
|  |  | 67 |  |                     yield return reader; | 
|  |  | 68 |  |                 } | 
|  |  | 69 |  |             } | 
|  |  | 70 |  |         } | 
|  |  | 71 |  |  | 
|  |  | 72 |  |         public static void Execute(this SqliteConnection sqliteConnection, string commandText) | 
|  |  | 73 |  |         { | 
|  | 0 | 74 |  |             using var command = sqliteConnection.CreateCommand(); | 
|  | 0 | 75 |  |             command.CommandText = commandText; | 
|  | 0 | 76 |  |             command.ExecuteNonQuery(); | 
|  | 0 | 77 |  |         } | 
|  |  | 78 |  |  | 
|  |  | 79 |  |         public static string ToDateTimeParamValue(this DateTime dateValue) | 
|  |  | 80 |  |         { | 
|  | 0 | 81 |  |             var kind = DateTimeKind.Utc; | 
|  |  | 82 |  |  | 
|  | 0 | 83 |  |             return (dateValue.Kind == DateTimeKind.Unspecified) | 
|  | 0 | 84 |  |                 ? DateTime.SpecifyKind(dateValue, kind).ToString( | 
|  | 0 | 85 |  |                     GetDateTimeKindFormat(kind), | 
|  | 0 | 86 |  |                     CultureInfo.InvariantCulture) | 
|  | 0 | 87 |  |                 : dateValue.ToString( | 
|  | 0 | 88 |  |                     GetDateTimeKindFormat(dateValue.Kind), | 
|  | 0 | 89 |  |                     CultureInfo.InvariantCulture); | 
|  |  | 90 |  |         } | 
|  |  | 91 |  |  | 
|  |  | 92 |  |         private static string GetDateTimeKindFormat(DateTimeKind kind) | 
|  | 0 | 93 |  |             => (kind == DateTimeKind.Utc) ? DatetimeFormatUtc : DatetimeFormatLocal; | 
|  |  | 94 |  |  | 
|  |  | 95 |  |         public static bool TryReadDateTime(this SqliteDataReader reader, int index, out DateTime result) | 
|  |  | 96 |  |         { | 
|  | 0 | 97 |  |             if (reader.IsDBNull(index)) | 
|  |  | 98 |  |             { | 
|  | 0 | 99 |  |                 result = default; | 
|  | 0 | 100 |  |                 return false; | 
|  |  | 101 |  |             } | 
|  |  | 102 |  |  | 
|  | 0 | 103 |  |             var dateText = reader.GetString(index); | 
|  |  | 104 |  |  | 
|  | 0 | 105 |  |             if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.Adju | 
|  |  | 106 |  |             { | 
|  |  | 107 |  |                 // If the resulting DateTimeKind is Unspecified it is actually Utc. | 
|  |  | 108 |  |                 // This is required downstream for the Json serializer. | 
|  | 0 | 109 |  |                 if (dateTimeResult.Kind == DateTimeKind.Unspecified) | 
|  |  | 110 |  |                 { | 
|  | 0 | 111 |  |                     dateTimeResult = DateTime.SpecifyKind(dateTimeResult, DateTimeKind.Utc); | 
|  |  | 112 |  |                 } | 
|  |  | 113 |  |  | 
|  | 0 | 114 |  |                 result = dateTimeResult; | 
|  | 0 | 115 |  |                 return true; | 
|  |  | 116 |  |             } | 
|  |  | 117 |  |  | 
|  | 0 | 118 |  |             result = default; | 
|  | 0 | 119 |  |             return false; | 
|  |  | 120 |  |         } | 
|  |  | 121 |  |  | 
|  |  | 122 |  |         public static bool TryGetGuid(this SqliteDataReader reader, int index, out Guid result) | 
|  |  | 123 |  |         { | 
|  | 0 | 124 |  |             if (reader.IsDBNull(index)) | 
|  |  | 125 |  |             { | 
|  | 0 | 126 |  |                 result = default; | 
|  | 0 | 127 |  |                 return false; | 
|  |  | 128 |  |             } | 
|  |  | 129 |  |  | 
|  |  | 130 |  |             try | 
|  |  | 131 |  |             { | 
|  | 0 | 132 |  |                 result = reader.GetGuid(index); | 
|  | 0 | 133 |  |                 return true; | 
|  |  | 134 |  |             } | 
|  | 0 | 135 |  |             catch | 
|  |  | 136 |  |             { | 
|  | 0 | 137 |  |                 result = Guid.Empty; | 
|  | 0 | 138 |  |                 return false; | 
|  |  | 139 |  |             } | 
|  | 0 | 140 |  |         } | 
|  |  | 141 |  |  | 
|  |  | 142 |  |         public static bool TryGetString(this SqliteDataReader reader, int index, out string result) | 
|  |  | 143 |  |         { | 
|  | 0 | 144 |  |             result = string.Empty; | 
|  |  | 145 |  |  | 
|  | 0 | 146 |  |             if (reader.IsDBNull(index)) | 
|  |  | 147 |  |             { | 
|  | 0 | 148 |  |                 return false; | 
|  |  | 149 |  |             } | 
|  |  | 150 |  |  | 
|  | 0 | 151 |  |             result = reader.GetString(index); | 
|  | 0 | 152 |  |             return true; | 
|  |  | 153 |  |         } | 
|  |  | 154 |  |  | 
|  |  | 155 |  |         public static bool TryGetBoolean(this SqliteDataReader reader, int index, out bool result) | 
|  |  | 156 |  |         { | 
|  | 0 | 157 |  |             if (reader.IsDBNull(index)) | 
|  |  | 158 |  |             { | 
|  | 0 | 159 |  |                 result = default; | 
|  | 0 | 160 |  |                 return false; | 
|  |  | 161 |  |             } | 
|  |  | 162 |  |  | 
|  | 0 | 163 |  |             result = reader.GetBoolean(index); | 
|  | 0 | 164 |  |             return true; | 
|  |  | 165 |  |         } | 
|  |  | 166 |  |  | 
|  |  | 167 |  |         public static bool TryGetInt32(this SqliteDataReader reader, int index, out int result) | 
|  |  | 168 |  |         { | 
|  | 0 | 169 |  |             if (reader.IsDBNull(index)) | 
|  |  | 170 |  |             { | 
|  | 0 | 171 |  |                 result = default; | 
|  | 0 | 172 |  |                 return false; | 
|  |  | 173 |  |             } | 
|  |  | 174 |  |  | 
|  | 0 | 175 |  |             result = reader.GetInt32(index); | 
|  | 0 | 176 |  |             return true; | 
|  |  | 177 |  |         } | 
|  |  | 178 |  |  | 
|  |  | 179 |  |         public static bool TryGetInt64(this SqliteDataReader reader, int index, out long result) | 
|  |  | 180 |  |         { | 
|  | 0 | 181 |  |             if (reader.IsDBNull(index)) | 
|  |  | 182 |  |             { | 
|  | 0 | 183 |  |                 result = default; | 
|  | 0 | 184 |  |                 return false; | 
|  |  | 185 |  |             } | 
|  |  | 186 |  |  | 
|  | 0 | 187 |  |             result = reader.GetInt64(index); | 
|  | 0 | 188 |  |             return true; | 
|  |  | 189 |  |         } | 
|  |  | 190 |  |  | 
|  |  | 191 |  |         public static bool TryGetSingle(this SqliteDataReader reader, int index, out float result) | 
|  |  | 192 |  |         { | 
|  | 0 | 193 |  |             if (reader.IsDBNull(index)) | 
|  |  | 194 |  |             { | 
|  | 0 | 195 |  |                 result = default; | 
|  | 0 | 196 |  |                 return false; | 
|  |  | 197 |  |             } | 
|  |  | 198 |  |  | 
|  | 0 | 199 |  |             result = reader.GetFloat(index); | 
|  | 0 | 200 |  |             return true; | 
|  |  | 201 |  |         } | 
|  |  | 202 |  |  | 
|  |  | 203 |  |         public static bool TryGetDouble(this SqliteDataReader reader, int index, out double result) | 
|  |  | 204 |  |         { | 
|  | 0 | 205 |  |             if (reader.IsDBNull(index)) | 
|  |  | 206 |  |             { | 
|  | 0 | 207 |  |                 result = default; | 
|  | 0 | 208 |  |                 return false; | 
|  |  | 209 |  |             } | 
|  |  | 210 |  |  | 
|  | 0 | 211 |  |             result = reader.GetDouble(index); | 
|  | 0 | 212 |  |             return true; | 
|  |  | 213 |  |         } | 
|  |  | 214 |  |  | 
|  |  | 215 |  |         public static void TryBind(this SqliteCommand statement, string name, Guid value) | 
|  |  | 216 |  |         { | 
|  | 0 | 217 |  |             statement.TryBind(name, value, true); | 
|  | 0 | 218 |  |         } | 
|  |  | 219 |  |  | 
|  |  | 220 |  |         public static void TryBind(this SqliteCommand statement, string name, object? value, bool isBlob = false) | 
|  |  | 221 |  |         { | 
|  | 0 | 222 |  |             var preparedValue = value ?? DBNull.Value; | 
|  | 0 | 223 |  |             if (statement.Parameters.Contains(name)) | 
|  |  | 224 |  |             { | 
|  | 0 | 225 |  |                 statement.Parameters[name].Value = preparedValue; | 
|  |  | 226 |  |             } | 
|  |  | 227 |  |             else | 
|  |  | 228 |  |             { | 
|  |  | 229 |  |                 // Blobs aren't always detected automatically | 
|  | 0 | 230 |  |                 if (isBlob) | 
|  |  | 231 |  |                 { | 
|  | 0 | 232 |  |                     statement.Parameters.Add(new SqliteParameter(name, SqliteType.Blob) { Value = value }); | 
|  |  | 233 |  |                 } | 
|  |  | 234 |  |                 else | 
|  |  | 235 |  |                 { | 
|  | 0 | 236 |  |                     statement.Parameters.AddWithValue(name, preparedValue); | 
|  |  | 237 |  |                 } | 
|  |  | 238 |  |             } | 
|  | 0 | 239 |  |         } | 
|  |  | 240 |  |  | 
|  |  | 241 |  |         public static void TryBindNull(this SqliteCommand statement, string name) | 
|  |  | 242 |  |         { | 
|  | 0 | 243 |  |             statement.TryBind(name, DBNull.Value); | 
|  | 0 | 244 |  |         } | 
|  |  | 245 |  |  | 
|  |  | 246 |  |         public static IEnumerable<SqliteDataReader> ExecuteQuery(this SqliteCommand command) | 
|  |  | 247 |  |         { | 
|  |  | 248 |  |             using (var reader = command.ExecuteReader()) | 
|  |  | 249 |  |             { | 
|  |  | 250 |  |                 while (reader.Read()) | 
|  |  | 251 |  |                 { | 
|  |  | 252 |  |                     yield return reader; | 
|  |  | 253 |  |                 } | 
|  |  | 254 |  |             } | 
|  |  | 255 |  |         } | 
|  |  | 256 |  |  | 
|  |  | 257 |  |         public static int SelectScalarInt(this SqliteCommand command) | 
|  |  | 258 |  |         { | 
|  | 0 | 259 |  |             var result = command.ExecuteScalar(); | 
|  |  | 260 |  |             // Can't be null since the method is used to retrieve Count | 
|  | 0 | 261 |  |             return Convert.ToInt32(result!, CultureInfo.InvariantCulture); | 
|  |  | 262 |  |         } | 
|  |  | 263 |  |  | 
|  |  | 264 |  |         public static SqliteCommand PrepareStatement(this SqliteConnection sqliteConnection, string sql) | 
|  |  | 265 |  |         { | 
|  | 0 | 266 |  |             var command = sqliteConnection.CreateCommand(); | 
|  | 0 | 267 |  |             command.CommandText = sql; | 
|  | 0 | 268 |  |             return command; | 
|  |  | 269 |  |         } | 
|  |  | 270 |  |     } | 
|  |  | 271 |  | } |