diff --git a/Directory.Packages.props b/Directory.Packages.props
index 210d6b814c..bd2a1d1811 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -27,6 +27,7 @@
+
diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
index d05534ee75..2ce87f5b46 100644
--- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
+++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
@@ -5,8 +5,8 @@
using System;
using System.Collections.Generic;
using Jellyfin.Extensions;
+using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Logging;
-using SQLitePCL.pretty;
namespace Emby.Server.Implementations.Data
{
@@ -45,24 +45,6 @@ namespace Emby.Server.Implementations.Data
/// The logger.
protected ILogger Logger { get; }
- ///
- /// Gets the default connection flags.
- ///
- /// The default connection flags.
- protected virtual ConnectionFlags DefaultConnectionFlags => ConnectionFlags.NoMutex;
-
- ///
- /// Gets the transaction mode.
- ///
- /// The transaction mode.>
- protected TransactionMode TransactionMode => TransactionMode.Deferred;
-
- ///
- /// Gets the transaction mode for read-only operations.
- ///
- /// The transaction mode.
- protected TransactionMode ReadTransactionMode => TransactionMode.Deferred;
-
///
/// Gets the cache size.
///
@@ -107,23 +89,8 @@ namespace Emby.Server.Implementations.Data
///
protected virtual SynchronousMode? Synchronous => SynchronousMode.Normal;
- ///
- /// Gets or sets the write lock.
- ///
- /// The write lock.
- protected ConnectionPool WriteConnections { get; set; }
-
- ///
- /// Gets or sets the write connection.
- ///
- /// The write connection.
- protected ConnectionPool ReadConnections { get; set; }
-
public virtual void Initialize()
{
- WriteConnections = new ConnectionPool(WriteConnectionsCount, CreateWriteConnection);
- ReadConnections = new ConnectionPool(ReadConnectionsCount, CreateReadConnection);
-
// Configuration and pragmas can affect VACUUM so it needs to be last.
using (var connection = GetConnection())
{
@@ -131,15 +98,9 @@ namespace Emby.Server.Implementations.Data
}
}
- protected ManagedConnection GetConnection(bool readOnly = false)
- => readOnly ? ReadConnections.GetConnection() : WriteConnections.GetConnection();
-
- protected SQLiteDatabaseConnection CreateWriteConnection()
+ protected SqliteConnection GetConnection(bool readOnly = false)
{
- var writeConnection = SQLite3.Open(
- DbFilePath,
- DefaultConnectionFlags | ConnectionFlags.Create | ConnectionFlags.ReadWrite,
- null);
+ var writeConnection = new SqliteConnection($"Filename={DbFilePath}");
if (CacheSize.HasValue)
{
@@ -176,50 +137,14 @@ namespace Emby.Server.Implementations.Data
return writeConnection;
}
- protected SQLiteDatabaseConnection CreateReadConnection()
+ public SqliteCommand PrepareStatement(SqliteConnection connection, string sql)
{
- var connection = SQLite3.Open(
- DbFilePath,
- DefaultConnectionFlags | ConnectionFlags.ReadOnly,
- null);
-
- if (CacheSize.HasValue)
- {
- connection.Execute("PRAGMA cache_size=" + CacheSize.Value);
- }
-
- if (!string.IsNullOrWhiteSpace(LockingMode))
- {
- connection.Execute("PRAGMA locking_mode=" + LockingMode);
- }
-
- if (!string.IsNullOrWhiteSpace(JournalMode))
- {
- connection.Execute("PRAGMA journal_mode=" + JournalMode);
- }
-
- if (JournalSizeLimit.HasValue)
- {
- connection.Execute("PRAGMA journal_size_limit=" + JournalSizeLimit.Value);
- }
-
- if (Synchronous.HasValue)
- {
- connection.Execute("PRAGMA synchronous=" + (int)Synchronous.Value);
- }
-
- connection.Execute("PRAGMA temp_store=" + (int)TempStore);
-
- return connection;
+ var command = connection.CreateCommand();
+ command.CommandText = sql;
+ return command;
}
- public IStatement PrepareStatement(ManagedConnection connection, string sql)
- => connection.PrepareStatement(sql);
-
- public IStatement PrepareStatement(IDatabaseConnection connection, string sql)
- => connection.PrepareStatement(sql);
-
- protected bool TableExists(ManagedConnection connection, string name)
+ protected bool TableExists(SqliteConnection connection, string name)
{
return connection.RunInTransaction(
db =>
@@ -236,11 +161,10 @@ namespace Emby.Server.Implementations.Data
}
return false;
- },
- ReadTransactionMode);
+ });
}
- protected List GetColumnNames(IDatabaseConnection connection, string table)
+ protected List GetColumnNames(SqliteConnection connection, string table)
{
var columnNames = new List();
@@ -255,7 +179,7 @@ namespace Emby.Server.Implementations.Data
return columnNames;
}
- protected void AddColumn(IDatabaseConnection connection, string table, string columnName, string type, List existingColumnNames)
+ protected void AddColumn(SqliteConnection connection, string table, string columnName, string type, List existingColumnNames)
{
if (existingColumnNames.Contains(columnName, StringComparison.OrdinalIgnoreCase))
{
@@ -291,12 +215,6 @@ namespace Emby.Server.Implementations.Data
return;
}
- if (dispose)
- {
- WriteConnections.Dispose();
- ReadConnections.Dispose();
- }
-
_disposed = true;
}
}
diff --git a/Emby.Server.Implementations/Data/ConnectionPool.cs b/Emby.Server.Implementations/Data/ConnectionPool.cs
deleted file mode 100644
index 5ea7e934ff..0000000000
--- a/Emby.Server.Implementations/Data/ConnectionPool.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using SQLitePCL.pretty;
-
-namespace Emby.Server.Implementations.Data;
-
-///
-/// A pool of SQLite Database connections.
-///
-public sealed class ConnectionPool : IDisposable
-{
- private readonly BlockingCollection _connections = new();
- private bool _disposed;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The number of database connection to create.
- /// Factory function to create the database connections.
- public ConnectionPool(int count, Func factory)
- {
- for (int i = 0; i < count; i++)
- {
- _connections.Add(factory.Invoke());
- }
- }
-
- ///
- /// Gets a database connection from the pool if one is available, otherwise blocks.
- ///
- /// A database connection.
- public ManagedConnection GetConnection()
- {
- if (_disposed)
- {
- ThrowObjectDisposedException();
- }
-
- return new ManagedConnection(_connections.Take(), this);
-
- static void ThrowObjectDisposedException()
- {
- throw new ObjectDisposedException(nameof(ConnectionPool));
- }
- }
-
- ///
- /// Return a database connection to the pool.
- ///
- /// The database connection to return.
- public void Return(SQLiteDatabaseConnection connection)
- {
- if (_disposed)
- {
- connection.Dispose();
- return;
- }
-
- _connections.Add(connection);
- }
-
- ///
- public void Dispose()
- {
- if (_disposed)
- {
- return;
- }
-
- foreach (var connection in _connections)
- {
- connection.Dispose();
- }
-
- _connections.Dispose();
-
- _disposed = true;
- }
-}
diff --git a/Emby.Server.Implementations/Data/ManagedConnection.cs b/Emby.Server.Implementations/Data/ManagedConnection.cs
deleted file mode 100644
index e84ed8f918..0000000000
--- a/Emby.Server.Implementations/Data/ManagedConnection.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using SQLitePCL.pretty;
-
-namespace Emby.Server.Implementations.Data
-{
- public sealed class ManagedConnection : IDisposable
- {
- private readonly ConnectionPool _pool;
-
- private SQLiteDatabaseConnection _db;
-
- private bool _disposed = false;
-
- public ManagedConnection(SQLiteDatabaseConnection db, ConnectionPool pool)
- {
- _db = db;
- _pool = pool;
- }
-
- public IStatement PrepareStatement(string sql)
- {
- return _db.PrepareStatement(sql);
- }
-
- public IEnumerable PrepareAll(string sql)
- {
- return _db.PrepareAll(sql);
- }
-
- public void ExecuteAll(string sql)
- {
- _db.ExecuteAll(sql);
- }
-
- public void Execute(string sql, params object[] values)
- {
- _db.Execute(sql, values);
- }
-
- public void RunQueries(string[] sql)
- {
- _db.RunQueries(sql);
- }
-
- public void RunInTransaction(Action action, TransactionMode mode)
- {
- _db.RunInTransaction(action, mode);
- }
-
- public T RunInTransaction(Func action, TransactionMode mode)
- {
- return _db.RunInTransaction(action, mode);
- }
-
- public IEnumerable> Query(string sql)
- {
- return _db.Query(sql);
- }
-
- public IEnumerable> Query(string sql, params object[] values)
- {
- return _db.Query(sql, values);
- }
-
- public void Dispose()
- {
- if (_disposed)
- {
- return;
- }
-
- _pool.Return(_db);
-
- _db = null!; // Don't dispose it
- _disposed = true;
- }
- }
-}
diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs
index 4055b0ba17..9e5cb17027 100644
--- a/Emby.Server.Implementations/Data/SqliteExtensions.cs
+++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs
@@ -1,11 +1,12 @@
-#nullable disable
+#nullable enable
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
+using System.Data;
using System.Diagnostics;
using System.Globalization;
-using SQLitePCL.pretty;
+using Microsoft.Data.Sqlite;
namespace Emby.Server.Implementations.Data
{
@@ -52,7 +53,68 @@ namespace Emby.Server.Implementations.Data
"yy-MM-dd"
};
- public static void RunQueries(this SQLiteDatabaseConnection connection, string[] queries)
+ private static void EnsureOpen(this SqliteConnection sqliteConnection)
+ {
+ if (sqliteConnection.State == ConnectionState.Closed)
+ {
+ sqliteConnection.Open();
+ }
+ }
+
+ public static IEnumerable Query(this SqliteConnection sqliteConnection, string commandText)
+ {
+ if (sqliteConnection.State != ConnectionState.Open)
+ {
+ sqliteConnection.Open();
+ }
+
+ var command = sqliteConnection.CreateCommand();
+ command.CommandText = commandText;
+ using (var reader = command.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ yield return reader;
+ }
+ }
+ }
+
+ public static void Execute(this SqliteConnection sqliteConnection, string commandText)
+ {
+ sqliteConnection.EnsureOpen();
+ var command = sqliteConnection.CreateCommand();
+ command.CommandText = commandText;
+ command.ExecuteNonQuery();
+ }
+
+ public static void RunInTransaction(this SqliteConnection sqliteConnection, Action action)
+ {
+ sqliteConnection.EnsureOpen();
+
+ using var transaction = sqliteConnection.BeginTransaction();
+ action(sqliteConnection);
+ transaction.Commit();
+ }
+
+ public static bool RunInTransaction(this SqliteConnection sqliteConnection, Func action)
+ {
+ sqliteConnection.EnsureOpen();
+ using var transaction = sqliteConnection.BeginTransaction();
+ var result = action(sqliteConnection);
+ transaction.Commit();
+ return result;
+ }
+
+ public static void ExecuteAll(this SqliteConnection sqliteConnection, string commandText)
+ {
+ sqliteConnection.EnsureOpen();
+
+ var command = sqliteConnection.CreateCommand();
+ command.CommandText = commandText;
+ command.ExecuteNonQuery();
+ }
+
+ public static void RunQueries(this SqliteConnection connection, string[] queries)
{
ArgumentNullException.ThrowIfNull(queries);
@@ -62,11 +124,6 @@ namespace Emby.Server.Implementations.Data
});
}
- public static Guid ReadGuidFromBlob(this ResultSetValue result)
- {
- return new Guid(result.ToBlob());
- }
-
public static string ToDateTimeParamValue(this DateTime dateValue)
{
var kind = DateTimeKind.Utc;
@@ -83,27 +140,26 @@ namespace Emby.Server.Implementations.Data
private static string GetDateTimeKindFormat(DateTimeKind kind)
=> (kind == DateTimeKind.Utc) ? DatetimeFormatUtc : DatetimeFormatLocal;
- public static DateTime ReadDateTime(this ResultSetValue result)
+ public static DateTime ReadDateTime(this SqliteDataReader result)
{
var dateText = result.ToString();
return DateTime.ParseExact(
- dateText,
+ dateText!,
_datetimeFormats,
DateTimeFormatInfo.InvariantInfo,
DateTimeStyles.AdjustToUniversal);
}
- public static bool TryReadDateTime(this IReadOnlyList reader, int index, out DateTime result)
+ public static bool TryReadDateTime(this SqliteDataReader reader, int index, out DateTime result)
{
- var item = reader[index];
- if (item.IsDbNull())
+ if (reader.IsDBNull(index))
{
result = default;
return false;
}
- var dateText = item.ToString();
+ var dateText = reader.GetString(index);
if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AdjustToUniversal, out var dateTimeResult))
{
@@ -115,335 +171,175 @@ namespace Emby.Server.Implementations.Data
return false;
}
- public static bool TryGetGuid(this IReadOnlyList reader, int index, out Guid result)
+ public static bool TryGetGuid(this SqliteDataReader reader, int index, out Guid result)
{
- var item = reader[index];
- if (item.IsDbNull())
+ if (reader.IsDBNull(index))
{
result = default;
return false;
}
- result = item.ReadGuidFromBlob();
+ result = reader.GetGuid(index);
return true;
}
- public static bool IsDbNull(this ResultSetValue result)
+ public static bool TryGetString(this SqliteDataReader reader, int index, out string result)
{
- return result.SQLiteType == SQLiteType.Null;
- }
+ result = string.Empty;
- public static string GetString(this IReadOnlyList result, int index)
- {
- return result[index].ToString();
- }
-
- public static bool TryGetString(this IReadOnlyList reader, int index, out string result)
- {
- result = null;
- var item = reader[index];
- if (item.IsDbNull())
+ if (reader.IsDBNull(index))
{
return false;
}
- result = item.ToString();
+ result = reader.GetString(index);
return true;
}
- public static bool GetBoolean(this IReadOnlyList result, int index)
+ public static bool TryGetBoolean(this SqliteDataReader reader, int index, out bool result)
{
- return result[index].ToBool();
- }
-
- public static bool TryGetBoolean(this IReadOnlyList reader, int index, out bool result)
- {
- var item = reader[index];
- if (item.IsDbNull())
+ if (reader.IsDBNull(index))
{
result = default;
return false;
}
- result = item.ToBool();
+ result = reader.GetBoolean(index);
return true;
}
- public static bool TryGetInt32(this IReadOnlyList reader, int index, out int result)
+ public static bool TryGetInt32(this SqliteDataReader reader, int index, out int result)
{
- var item = reader[index];
- if (item.IsDbNull())
+ if (reader.IsDBNull(index))
{
result = default;
return false;
}
- result = item.ToInt();
+ result = reader.GetInt32(index);
return true;
}
- public static long GetInt64(this IReadOnlyList result, int index)
+ public static bool TryGetInt64(this SqliteDataReader reader, int index, out long result)
{
- return result[index].ToInt64();
- }
-
- public static bool TryGetInt64(this IReadOnlyList reader, int index, out long result)
- {
- var item = reader[index];
- if (item.IsDbNull())
+ if (reader.IsDBNull(index))
{
result = default;
return false;
}
- result = item.ToInt64();
+ result = reader.GetInt64(index);
return true;
}
- public static bool TryGetSingle(this IReadOnlyList reader, int index, out float result)
+ public static bool TryGetSingle(this SqliteDataReader reader, int index, out float result)
{
- var item = reader[index];
- if (item.IsDbNull())
+ if (reader.IsDBNull(index))
{
result = default;
return false;
}
- result = item.ToFloat();
+ result = reader.GetFloat(index);
return true;
}
- public static bool TryGetDouble(this IReadOnlyList reader, int index, out double result)
+ public static bool TryGetDouble(this SqliteDataReader reader, int index, out double result)
{
- var item = reader[index];
- if (item.IsDbNull())
+ if (reader.IsDBNull(index))
{
result = default;
return false;
}
- result = item.ToDouble();
+ result = reader.GetDouble(index);
return true;
}
- public static Guid GetGuid(this IReadOnlyList result, int index)
- {
- return result[index].ReadGuidFromBlob();
- }
-
[Conditional("DEBUG")]
private static void CheckName(string name)
{
throw new ArgumentException("Invalid param name: " + name, nameof(name));
}
- public static void TryBind(this IStatement statement, string name, double value)
+ public static void TryBind(this SqliteCommand statement, string name, Guid value)
{
- if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
+ if (statement.Parameters.Contains(name))
{
- bindParam.Bind(value);
+ statement.Parameters[name].Value = value;
}
else
{
- CheckName(name);
+ statement.Parameters.Add(new SqliteParameter(name, SqliteType.Blob) { Value = value });
}
}
- public static void TryBind(this IStatement statement, string name, string value)
+ public static void TryBind(this SqliteCommand statement, string name, object? value)
{
- if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
+ var preparedValue = value ?? DBNull.Value;
+ if (statement.Parameters.Contains(name))
{
- if (value is null)
+ statement.Parameters[name].Value = preparedValue;
+ }
+ else
+ {
+ statement.Parameters.AddWithValue(name, preparedValue);
+ }
+ }
+
+ public static void TryBind(this SqliteCommand statement, string name, byte[] value)
+ {
+ if (statement.Parameters.Contains(name))
+ {
+ statement.Parameters[name].Value = value;
+ }
+ else
+ {
+ statement.Parameters.Add(new SqliteParameter(name, SqliteType.Blob, value.Length) { Value = value });
+ }
+ }
+
+ public static void TryBindNull(this SqliteCommand statement, string name)
+ {
+ statement.TryBind(name, DBNull.Value);
+ }
+
+ public static IEnumerable ExecuteQuery(this SqliteCommand command)
+ {
+ using (var reader = command.ExecuteReader())
+ {
+ while (reader.Read())
{
- bindParam.BindNull();
- }
- else
- {
- bindParam.Bind(value);
+ yield return reader;
}
}
- else
- {
- CheckName(name);
- }
}
- public static void TryBind(this IStatement statement, string name, bool value)
+ public static int SelectScalarInt(this SqliteCommand command)
{
- if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
- {
- bindParam.Bind(value);
- }
- else
- {
- CheckName(name);
- }
+ var result = command.ExecuteScalar();
+ return Convert.ToInt32(result!, CultureInfo.InvariantCulture);
}
- public static void TryBind(this IStatement statement, string name, float value)
+ public static SqliteCommand PrepareStatement(this SqliteConnection sqliteConnection, string sql)
{
- if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
- {
- bindParam.Bind(value);
- }
- else
- {
- CheckName(name);
- }
+ sqliteConnection.EnsureOpen();
+ var command = sqliteConnection.CreateCommand();
+ command.CommandText = sql;
+ return command;
}
- public static void TryBind(this IStatement statement, string name, int value)
+ // Hacky
+ public static void MoveNext(this SqliteCommand sqliteCommand)
{
- if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
- {
- bindParam.Bind(value);
- }
- else
- {
- CheckName(name);
- }
+ sqliteCommand.Prepare();
+ var result = sqliteCommand.ExecuteNonQuery();
}
- public static void TryBind(this IStatement statement, string name, Guid value)
+ public static byte[] GetBlob(this SqliteDataReader reader, int index)
{
- if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
- {
- Span byteValue = stackalloc byte[16];
- value.TryWriteBytes(byteValue);
- bindParam.Bind(byteValue);
- }
- else
- {
- CheckName(name);
- }
- }
-
- public static void TryBind(this IStatement statement, string name, DateTime value)
- {
- if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
- {
- bindParam.Bind(value.ToDateTimeParamValue());
- }
- else
- {
- CheckName(name);
- }
- }
-
- public static void TryBind(this IStatement statement, string name, long value)
- {
- if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
- {
- bindParam.Bind(value);
- }
- else
- {
- CheckName(name);
- }
- }
-
- public static void TryBind(this IStatement statement, string name, ReadOnlySpan value)
- {
- if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
- {
- bindParam.Bind(value);
- }
- else
- {
- CheckName(name);
- }
- }
-
- public static void TryBindNull(this IStatement statement, string name)
- {
- if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
- {
- bindParam.BindNull();
- }
- else
- {
- CheckName(name);
- }
- }
-
- public static void TryBind(this IStatement statement, string name, DateTime? value)
- {
- if (value.HasValue)
- {
- TryBind(statement, name, value.Value);
- }
- else
- {
- TryBindNull(statement, name);
- }
- }
-
- public static void TryBind(this IStatement statement, string name, Guid? value)
- {
- if (value.HasValue)
- {
- TryBind(statement, name, value.Value);
- }
- else
- {
- TryBindNull(statement, name);
- }
- }
-
- public static void TryBind(this IStatement statement, string name, double? value)
- {
- if (value.HasValue)
- {
- TryBind(statement, name, value.Value);
- }
- else
- {
- TryBindNull(statement, name);
- }
- }
-
- public static void TryBind(this IStatement statement, string name, int? value)
- {
- if (value.HasValue)
- {
- TryBind(statement, name, value.Value);
- }
- else
- {
- TryBindNull(statement, name);
- }
- }
-
- public static void TryBind(this IStatement statement, string name, float? value)
- {
- if (value.HasValue)
- {
- TryBind(statement, name, value.Value);
- }
- else
- {
- TryBindNull(statement, name);
- }
- }
-
- public static void TryBind(this IStatement statement, string name, bool? value)
- {
- if (value.HasValue)
- {
- TryBind(statement, name, value.Value);
- }
- else
- {
- TryBindNull(statement, name);
- }
- }
-
- public static IEnumerable> ExecuteQuery(this IStatement statement)
- {
- while (statement.MoveNext())
- {
- yield return statement.Current;
- }
+ // Have to reset to casting as there isn't a publicly available GetBlob method
+ return (byte[])reader.GetValue(index);
}
}
}
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index 73ec856fc8..4ceaafa128 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -35,9 +35,9 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying;
+using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
-using SQLitePCL.pretty;
namespace Emby.Server.Implementations.Data
{
@@ -555,8 +555,7 @@ namespace Emby.Server.Implementations.Data
AddColumn(db, "MediaStreams", "DvBlSignalCompatibilityId", "INT", existingColumnNames);
AddColumn(db, "MediaStreams", "IsHearingImpaired", "BIT", existingColumnNames);
- },
- TransactionMode);
+ });
connection.RunQueries(postQueries);
}
@@ -580,8 +579,7 @@ namespace Emby.Server.Implementations.Data
saveImagesStatement.MoveNext();
}
- },
- TransactionMode);
+ });
}
}
@@ -624,12 +622,11 @@ namespace Emby.Server.Implementations.Data
db =>
{
SaveItemsInTransaction(db, tuples);
- },
- TransactionMode);
+ });
}
}
- private void SaveItemsInTransaction(IDatabaseConnection db, IEnumerable<(BaseItem Item, List AncestorIds, BaseItem TopParent, string UserDataKey, List InheritedTags)> tuples)
+ private void SaveItemsInTransaction(SqliteConnection db, IEnumerable<(BaseItem Item, List AncestorIds, BaseItem TopParent, string UserDataKey, List InheritedTags)> tuples)
{
using (var saveItemStatement = PrepareStatement(db, SaveItemCommandText))
using (var deleteAncestorsStatement = PrepareStatement(db, "delete from AncestorIds where ItemId=@ItemId"))
@@ -639,7 +636,7 @@ namespace Emby.Server.Implementations.Data
{
if (requiresReset)
{
- saveItemStatement.Reset();
+ // TODO saveItemStatement.Parameters.Clear();
}
var item = tuple.Item;
@@ -677,7 +674,7 @@ namespace Emby.Server.Implementations.Data
return _appHost.ExpandVirtualPath(path);
}
- private void SaveItem(BaseItem item, BaseItem topParent, string userDataKey, IStatement saveItemStatement)
+ private void SaveItem(BaseItem item, BaseItem topParent, string userDataKey, SqliteCommand saveItemStatement)
{
Type type = item.GetType();
@@ -1389,12 +1386,12 @@ namespace Emby.Server.Implementations.Data
return true;
}
- private BaseItem GetItem(IReadOnlyList reader, InternalItemsQuery query)
+ private BaseItem GetItem(SqliteDataReader reader, InternalItemsQuery query)
{
return GetItem(reader, query, HasProgramAttributes(query), HasEpisodeAttributes(query), HasServiceName(query), HasStartDate(query), HasTrailerTypes(query), HasArtistFields(query), HasSeriesFields(query));
}
- private BaseItem GetItem(IReadOnlyList reader, InternalItemsQuery query, bool enableProgramAttributes, bool hasEpisodeAttributes, bool hasServiceName, bool queryHasStartDate, bool hasTrailerTypes, bool hasArtistFields, bool hasSeriesFields)
+ private BaseItem GetItem(SqliteDataReader reader, InternalItemsQuery query, bool enableProgramAttributes, bool hasEpisodeAttributes, bool hasServiceName, bool queryHasStartDate, bool hasTrailerTypes, bool hasArtistFields, bool hasSeriesFields)
{
var typeString = reader.GetString(0);
@@ -1411,7 +1408,7 @@ namespace Emby.Server.Implementations.Data
{
try
{
- item = JsonSerializer.Deserialize(reader[1].ToBlob(), type, _jsonOptions) as BaseItem;
+ item = JsonSerializer.Deserialize(reader.GetStream(1), type, _jsonOptions) as BaseItem;
}
catch (JsonException ex)
{
@@ -1452,17 +1449,9 @@ namespace Emby.Server.Implementations.Data
item.EndDate = endDate;
}
- var channelId = reader[index];
- if (!channelId.IsDbNull())
+ if (reader.TryGetGuid(index, out var guid))
{
- if (!Utf8Parser.TryParse(channelId.ToBlob(), out Guid value, out _, standardFormat: 'N'))
- {
- var str = reader.GetString(index);
- Logger.LogWarning("{ChannelId} isn't in the expected format", str);
- value = new Guid(str);
- }
-
- item.ChannelId = value;
+ item.ChannelId = guid;
}
index++;
@@ -2018,7 +2007,7 @@ namespace Emby.Server.Implementations.Data
/// The reader.
/// The item.
/// ChapterInfo.
- private ChapterInfo GetChapter(IReadOnlyList reader, BaseItem item)
+ private ChapterInfo GetChapter(SqliteDataReader reader, BaseItem item)
{
var chapter = new ChapterInfo
{
@@ -2071,23 +2060,22 @@ namespace Emby.Server.Implementations.Data
ArgumentNullException.ThrowIfNull(chapters);
- var idBlob = id.ToByteArray();
-
using (var connection = GetConnection())
{
connection.RunInTransaction(
db =>
{
// First delete chapters
- db.Execute("delete from " + ChaptersTableName + " where ItemId=@ItemId", idBlob);
+ var command = db.PrepareStatement($"delete from {ChaptersTableName} where ItemId=@ItemId");
+ command.TryBind("@ItemId", id);
+ command.ExecuteNonQuery();
- InsertChapters(idBlob, chapters, db);
- },
- TransactionMode);
+ InsertChapters(id, chapters, db);
+ });
}
}
- private void InsertChapters(byte[] idBlob, IReadOnlyList chapters, IDatabaseConnection db)
+ private void InsertChapters(Guid idBlob, IReadOnlyList chapters, SqliteConnection db)
{
var startIndex = 0;
var limit = 100;
@@ -2126,7 +2114,7 @@ namespace Emby.Server.Implementations.Data
chapterIndex++;
}
- statement.Reset();
+ // TODO statement.Parameters.Clear();
statement.MoveNext();
}
@@ -2463,7 +2451,7 @@ namespace Emby.Server.Implementations.Data
}
}
- private void BindSearchParams(InternalItemsQuery query, IStatement statement)
+ private void BindSearchParams(InternalItemsQuery query, SqliteCommand statement)
{
var searchTerm = query.SearchTerm;
@@ -2475,7 +2463,7 @@ namespace Emby.Server.Implementations.Data
searchTerm = FixUnicodeChars(searchTerm);
searchTerm = GetCleanValue(searchTerm);
- var commandText = statement.SQL;
+ var commandText = statement.CommandText;
if (commandText.Contains("@SearchTermStartsWith", StringComparison.OrdinalIgnoreCase))
{
statement.TryBind("@SearchTermStartsWith", searchTerm + "%");
@@ -2492,7 +2480,7 @@ namespace Emby.Server.Implementations.Data
}
}
- private void BindSimilarParams(InternalItemsQuery query, IStatement statement)
+ private void BindSimilarParams(InternalItemsQuery query, SqliteCommand statement)
{
var item = query.SimilarTo;
@@ -2501,7 +2489,7 @@ namespace Emby.Server.Implementations.Data
return;
}
- var commandText = statement.SQL;
+ var commandText = statement.CommandText;
if (commandText.Contains("@ItemOfficialRating", StringComparison.OrdinalIgnoreCase))
{
@@ -2598,7 +2586,7 @@ namespace Emby.Server.Implementations.Data
// Running this again will bind the params
GetWhereClauses(query, statement);
- return statement.ExecuteQuery().SelectScalarInt().First();
+ return statement.SelectScalarInt();
}
}
@@ -2916,11 +2904,10 @@ namespace Emby.Server.Implementations.Data
// Running this again will bind the params
GetWhereClauses(query, statement);
- result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
+ result.TotalRecordCount = statement.SelectScalarInt();
}
}
- },
- ReadTransactionMode);
+ });
}
result.StartIndex = query.StartIndex ?? 0;
@@ -3188,7 +3175,7 @@ namespace Emby.Server.Implementations.Data
foreach (var row in statement.ExecuteQuery())
{
- list.Add(row[0].ReadGuidFromBlob());
+ list.Add(row.GetGuid(0));
}
}
@@ -3224,7 +3211,7 @@ namespace Emby.Server.Implementations.Data
}
#nullable enable
- private List GetWhereClauses(InternalItemsQuery query, IStatement? statement)
+ private List GetWhereClauses(InternalItemsQuery query, SqliteCommand? statement)
{
if (query.IsResumable ?? false)
{
@@ -3647,8 +3634,7 @@ namespace Emby.Server.Implementations.Data
if (statement is not null)
{
- query.PersonIds[i].TryWriteBytes(idBytes);
- statement.TryBind(paramName, idBytes);
+ statement.TryBind(paramName, query.PersonIds[i]);
}
}
@@ -4696,8 +4682,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
db =>
{
connection.ExecuteAll(sql);
- },
- TransactionMode);
+ });
}
}
@@ -4735,16 +4720,15 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
// Delete the item
ExecuteWithSingleParam(db, "delete from TypedBaseItems where guid=@Id", idBlob);
- },
- TransactionMode);
+ });
}
}
- private void ExecuteWithSingleParam(IDatabaseConnection db, string query, ReadOnlySpan value)
+ private void ExecuteWithSingleParam(SqliteConnection db, string query, ReadOnlySpan value)
{
using (var statement = PrepareStatement(db, query))
{
- statement.TryBind("@Id", value);
+ statement.TryBind("@Id", value.ToArray());
statement.MoveNext();
}
@@ -4826,7 +4810,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
return list;
}
- private List GetPeopleWhereClauses(InternalPeopleQuery query, IStatement statement)
+ private List GetPeopleWhereClauses(InternalPeopleQuery query, SqliteCommand statement)
{
var whereClauses = new List();
@@ -4896,7 +4880,7 @@ AND Type = @InternalPersonType)");
return whereClauses;
}
- private void UpdateAncestors(Guid itemId, List ancestorIds, IDatabaseConnection db, IStatement deleteAncestorsStatement)
+ private void UpdateAncestors(Guid itemId, List ancestorIds, SqliteConnection db, SqliteCommand deleteAncestorsStatement)
{
if (itemId.Equals(default))
{
@@ -4907,12 +4891,14 @@ AND Type = @InternalPersonType)");
CheckDisposed();
- Span itemIdBlob = stackalloc byte[16];
- itemId.TryWriteBytes(itemIdBlob);
+ // TODO how to handle span?
+ Span itemIdBlob2 = stackalloc byte[16];
+ itemId.TryWriteBytes(itemIdBlob2);
+ var itemIdBlob = Encoding.ASCII.GetBytes(itemId.ToString());
// First delete
- deleteAncestorsStatement.Reset();
- deleteAncestorsStatement.TryBind("@ItemId", itemIdBlob);
+ // TODO deleteAncestorsStatement.Parameters.Clear();
+ deleteAncestorsStatement.TryBind("@ItemId", itemId);
deleteAncestorsStatement.MoveNext();
if (ancestorIds.Count == 0)
@@ -4942,13 +4928,13 @@ AND Type = @InternalPersonType)");
var index = i.ToString(CultureInfo.InvariantCulture);
var ancestorId = ancestorIds[i];
- ancestorId.TryWriteBytes(itemIdBlob);
+ itemIdBlob = Encoding.ASCII.GetBytes(itemId.ToString());
- statement.TryBind("@AncestorId" + index, itemIdBlob);
+ statement.TryBind("@AncestorId" + index, ancestorId);
statement.TryBind("@AncestorIdText" + index, ancestorId.ToString("N", CultureInfo.InvariantCulture));
}
- statement.Reset();
+ // TODO statement.Parameters.Clear();
statement.MoveNext();
}
}
@@ -5323,11 +5309,10 @@ AND Type = @InternalPersonType)");
GetWhereClauses(innerQuery, statement);
GetWhereClauses(outerQuery, statement);
- result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
+ result.TotalRecordCount = statement.SelectScalarInt();
}
}
- },
- ReadTransactionMode);
+ });
}
if (result.TotalRecordCount == 0)
@@ -5341,7 +5326,7 @@ AND Type = @InternalPersonType)");
return result;
}
- private static ItemCounts GetItemCounts(IReadOnlyList reader, int countStartColumn, BaseItemKind[] typesToCount)
+ private static ItemCounts GetItemCounts(SqliteDataReader reader, int countStartColumn, BaseItemKind[] typesToCount)
{
var counts = new ItemCounts();
@@ -5420,7 +5405,7 @@ AND Type = @InternalPersonType)");
return list;
}
- private void UpdateItemValues(Guid itemId, List<(int MagicNumber, string Value)> values, IDatabaseConnection db)
+ private void UpdateItemValues(Guid itemId, List<(int MagicNumber, string Value)> values, SqliteConnection db)
{
if (itemId.Equals(default))
{
@@ -5434,12 +5419,14 @@ AND Type = @InternalPersonType)");
var guidBlob = itemId.ToByteArray();
// First delete
- db.Execute("delete from ItemValues where ItemId=@Id", guidBlob);
+ using var command = db.PrepareStatement("delete from ItemValues where ItemId=@Id");
+ command.TryBind("@Id", guidBlob);
+ command.ExecuteNonQuery();
InsertItemValues(guidBlob, values, db);
}
- private void InsertItemValues(byte[] idBlob, List<(int MagicNumber, string Value)> values, IDatabaseConnection db)
+ private void InsertItemValues(byte[] idBlob, List<(int MagicNumber, string Value)> values, SqliteConnection db)
{
const int Limit = 100;
var startIndex = 0;
@@ -5484,7 +5471,7 @@ AND Type = @InternalPersonType)");
statement.TryBind("@CleanValue" + index, GetCleanValue(itemValue));
}
- statement.Reset();
+ // TODO statement.Parameters.Clear();
statement.MoveNext();
}
@@ -5512,15 +5499,17 @@ AND Type = @InternalPersonType)");
var itemIdBlob = itemId.ToByteArray();
// First delete chapters
- db.Execute("delete from People where ItemId=@ItemId", itemIdBlob);
+ using var command = db.CreateCommand();
+ command.CommandText = "delete from People where ItemId=@ItemId";
+ command.TryBind("@ItemId", itemIdBlob);
+ command.ExecuteNonQuery();
InsertPeople(itemIdBlob, people, db);
- },
- TransactionMode);
+ });
}
}
- private void InsertPeople(byte[] idBlob, List people, IDatabaseConnection db)
+ private void InsertPeople(byte[] idBlob, List people, SqliteConnection db)
{
const int Limit = 100;
var startIndex = 0;
@@ -5561,7 +5550,6 @@ AND Type = @InternalPersonType)");
listIndex++;
}
- statement.Reset();
statement.MoveNext();
}
@@ -5570,7 +5558,7 @@ AND Type = @InternalPersonType)");
}
}
- private PersonInfo GetPerson(IReadOnlyList reader)
+ private PersonInfo GetPerson(SqliteDataReader reader)
{
var item = new PersonInfo
{
@@ -5666,15 +5654,16 @@ AND Type = @InternalPersonType)");
var itemIdBlob = id.ToByteArray();
// Delete existing mediastreams
- db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob);
+ using var command = db.PrepareStatement("delete from mediastreams where ItemId=@ItemId");
+ command.TryBind("@ItemId", itemIdBlob);
+ command.ExecuteNonQuery();
InsertMediaStreams(itemIdBlob, streams, db);
- },
- TransactionMode);
+ });
}
}
- private void InsertMediaStreams(byte[] idBlob, IReadOnlyList streams, IDatabaseConnection db)
+ private void InsertMediaStreams(byte[] idBlob, IReadOnlyList streams, SqliteConnection db)
{
const int Limit = 10;
var startIndex = 0;
@@ -5770,7 +5759,7 @@ AND Type = @InternalPersonType)");
statement.TryBind("@IsHearingImpaired" + index, stream.IsHearingImpaired);
}
- statement.Reset();
+ // TODO statement.Parameters.Clear();
statement.MoveNext();
}
@@ -5784,15 +5773,14 @@ AND Type = @InternalPersonType)");
///
/// The reader.
/// MediaStream.
- private MediaStream GetMediaStream(IReadOnlyList reader)
+ private MediaStream GetMediaStream(SqliteDataReader reader)
{
var item = new MediaStream
{
- Index = reader[1].ToInt()
+ Index = reader.GetInt32(1),
+ Type = Enum.Parse(reader.GetString(2), true)
};
- item.Type = Enum.Parse(reader[2].ToString(), true);
-
if (reader.TryGetString(3, out var codec))
{
item.Codec = codec;
@@ -6050,18 +6038,19 @@ AND Type = @InternalPersonType)");
{
var itemIdBlob = id.ToByteArray();
- db.Execute("delete from mediaattachments where ItemId=@ItemId", itemIdBlob);
+ using var command = db.PrepareStatement("delete from mediaattachments where ItemId=@ItemId");
+ command.TryBind("@ItemId", itemIdBlob);
+ command.ExecuteNonQuery();
InsertMediaAttachments(itemIdBlob, attachments, db, cancellationToken);
- },
- TransactionMode);
+ });
}
}
private void InsertMediaAttachments(
byte[] idBlob,
IReadOnlyList attachments,
- IDatabaseConnection db,
+ SqliteConnection db,
CancellationToken cancellationToken)
{
const int InsertAtOnce = 10;
@@ -6111,7 +6100,7 @@ AND Type = @InternalPersonType)");
statement.TryBind("@MIMEType" + index, attachment.MimeType);
}
- statement.Reset();
+ // TODO statement.Parameters.Clear();
statement.MoveNext();
}
@@ -6124,11 +6113,11 @@ AND Type = @InternalPersonType)");
///
/// The reader.
/// MediaAttachment.
- private MediaAttachment GetMediaAttachment(IReadOnlyList reader)
+ private MediaAttachment GetMediaAttachment(SqliteDataReader reader)
{
var item = new MediaAttachment
{
- Index = reader[1].ToInt()
+ Index = reader.GetInt32(1)
};
if (reader.TryGetString(2, out var codec))
diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
index a1e217ad14..bc3863a65f 100644
--- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
@@ -11,8 +11,8 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
+using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Logging;
-using SQLitePCL.pretty;
namespace Emby.Server.Implementations.Data
{
@@ -80,12 +80,11 @@ namespace Emby.Server.Implementations.Data
db.ExecuteAll("INSERT INTO UserDatas (key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex) SELECT key, InternalUserId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex from userdata where InternalUserId not null");
}
}
- },
- TransactionMode);
+ });
}
}
- private void ImportUserIds(IDatabaseConnection db, IEnumerable users)
+ private void ImportUserIds(SqliteConnection db, IEnumerable users)
{
var userIdsWithUserData = GetAllUserIdsWithUserData(db);
@@ -100,14 +99,14 @@ namespace Emby.Server.Implementations.Data
statement.TryBind("@UserId", user.Id);
statement.TryBind("@InternalUserId", user.InternalId);
+ statement.Prepare();
- statement.MoveNext();
- statement.Reset();
+ statement.ExecuteNonQuery();
}
}
}
- private List GetAllUserIdsWithUserData(IDatabaseConnection db)
+ private List GetAllUserIdsWithUserData(SqliteConnection db)
{
var list = new List();
@@ -117,7 +116,7 @@ namespace Emby.Server.Implementations.Data
{
try
{
- list.Add(row[0].ReadGuidFromBlob());
+ list.Add(row.GetGuid(0));
}
catch (Exception ex)
{
@@ -174,12 +173,11 @@ namespace Emby.Server.Implementations.Data
db =>
{
SaveUserData(db, internalUserId, key, userData);
- },
- TransactionMode);
+ });
}
}
- private static void SaveUserData(IDatabaseConnection db, long internalUserId, string key, UserItemData userData)
+ private static void SaveUserData(SqliteConnection db, long internalUserId, string key, UserItemData userData)
{
using (var statement = db.PrepareStatement("replace into UserDatas (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate,@AudioStreamIndex,@SubtitleStreamIndex)"))
{
@@ -247,8 +245,7 @@ namespace Emby.Server.Implementations.Data
{
SaveUserData(db, internalUserId, userItemData.Key, userItemData);
}
- },
- TransactionMode);
+ });
}
}
@@ -336,7 +333,7 @@ namespace Emby.Server.Implementations.Data
///
/// The list of result set values.
/// The user item data.
- private UserItemData ReadRow(IReadOnlyList reader)
+ private UserItemData ReadRow(SqliteDataReader reader)
{
var userData = new UserItemData();
@@ -348,10 +345,10 @@ namespace Emby.Server.Implementations.Data
userData.Rating = rating;
}
- userData.Played = reader[3].ToBool();
- userData.PlayCount = reader[4].ToInt();
- userData.IsFavorite = reader[5].ToBool();
- userData.PlaybackPositionTicks = reader[6].ToInt64();
+ userData.Played = reader.GetBoolean(3);
+ userData.PlayCount = reader.GetInt32(4);
+ userData.IsFavorite = reader.GetBoolean(5);
+ userData.PlaybackPositionTicks = reader.GetInt64(6);
if (reader.TryReadDateTime(7, out var lastPlayedDate))
{
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index b8655c7600..3aab0a5e9d 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -24,6 +24,7 @@
+
@@ -31,7 +32,6 @@
-
diff --git a/Jellyfin.Server/Extensions/SqliteExtensions.cs b/Jellyfin.Server/Extensions/SqliteExtensions.cs
new file mode 100644
index 0000000000..0e6a1bb13f
--- /dev/null
+++ b/Jellyfin.Server/Extensions/SqliteExtensions.cs
@@ -0,0 +1,449 @@
+#nullable disable
+#pragma warning disable CS1591
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using SQLitePCL.pretty;
+
+namespace Jellyfin.Server.Extensions
+{
+ public static class SqliteExtensions
+ {
+ private const string DatetimeFormatUtc = "yyyy-MM-dd HH:mm:ss.FFFFFFFK";
+ private const string DatetimeFormatLocal = "yyyy-MM-dd HH:mm:ss.FFFFFFF";
+
+ ///
+ /// An array of ISO-8601 DateTime formats that we support parsing.
+ ///
+ private static readonly string[] _datetimeFormats = new string[]
+ {
+ "THHmmssK",
+ "THHmmK",
+ "HH:mm:ss.FFFFFFFK",
+ "HH:mm:ssK",
+ "HH:mmK",
+ DatetimeFormatUtc,
+ "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",
+ DatetimeFormatLocal,
+ "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"
+ };
+
+ public static void RunQueries(this SQLiteDatabaseConnection connection, string[] queries)
+ {
+ ArgumentNullException.ThrowIfNull(queries);
+
+ connection.RunInTransaction(conn =>
+ {
+ conn.ExecuteAll(string.Join(';', queries));
+ });
+ }
+
+ public static Guid ReadGuidFromBlob(this ResultSetValue result)
+ {
+ return new Guid(result.ToBlob());
+ }
+
+ public static string ToDateTimeParamValue(this DateTime dateValue)
+ {
+ var kind = DateTimeKind.Utc;
+
+ return (dateValue.Kind == DateTimeKind.Unspecified)
+ ? DateTime.SpecifyKind(dateValue, kind).ToString(
+ GetDateTimeKindFormat(kind),
+ CultureInfo.InvariantCulture)
+ : dateValue.ToString(
+ GetDateTimeKindFormat(dateValue.Kind),
+ CultureInfo.InvariantCulture);
+ }
+
+ private static string GetDateTimeKindFormat(DateTimeKind kind)
+ => (kind == DateTimeKind.Utc) ? DatetimeFormatUtc : DatetimeFormatLocal;
+
+ public static DateTime ReadDateTime(this ResultSetValue result)
+ {
+ var dateText = result.ToString();
+
+ return DateTime.ParseExact(
+ dateText,
+ _datetimeFormats,
+ DateTimeFormatInfo.InvariantInfo,
+ DateTimeStyles.AdjustToUniversal);
+ }
+
+ public static bool TryReadDateTime(this IReadOnlyList reader, int index, out DateTime result)
+ {
+ var item = reader[index];
+ if (item.IsDbNull())
+ {
+ result = default;
+ return false;
+ }
+
+ var dateText = item.ToString();
+
+ if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AdjustToUniversal, out var dateTimeResult))
+ {
+ result = dateTimeResult;
+ return true;
+ }
+
+ result = default;
+ return false;
+ }
+
+ public static bool TryGetGuid(this IReadOnlyList reader, int index, out Guid result)
+ {
+ var item = reader[index];
+ if (item.IsDbNull())
+ {
+ result = default;
+ return false;
+ }
+
+ result = item.ReadGuidFromBlob();
+ return true;
+ }
+
+ public static bool IsDbNull(this ResultSetValue result)
+ {
+ return result.SQLiteType == SQLiteType.Null;
+ }
+
+ public static string GetString(this IReadOnlyList result, int index)
+ {
+ return result[index].ToString();
+ }
+
+ public static bool TryGetString(this IReadOnlyList reader, int index, out string result)
+ {
+ result = null;
+ var item = reader[index];
+ if (item.IsDbNull())
+ {
+ return false;
+ }
+
+ result = item.ToString();
+ return true;
+ }
+
+ public static bool GetBoolean(this IReadOnlyList result, int index)
+ {
+ return result[index].ToBool();
+ }
+
+ public static bool TryGetBoolean(this IReadOnlyList reader, int index, out bool result)
+ {
+ var item = reader[index];
+ if (item.IsDbNull())
+ {
+ result = default;
+ return false;
+ }
+
+ result = item.ToBool();
+ return true;
+ }
+
+ public static bool TryGetInt32(this IReadOnlyList reader, int index, out int result)
+ {
+ var item = reader[index];
+ if (item.IsDbNull())
+ {
+ result = default;
+ return false;
+ }
+
+ result = item.ToInt();
+ return true;
+ }
+
+ public static long GetInt64(this IReadOnlyList result, int index)
+ {
+ return result[index].ToInt64();
+ }
+
+ public static bool TryGetInt64(this IReadOnlyList reader, int index, out long result)
+ {
+ var item = reader[index];
+ if (item.IsDbNull())
+ {
+ result = default;
+ return false;
+ }
+
+ result = item.ToInt64();
+ return true;
+ }
+
+ public static bool TryGetSingle(this IReadOnlyList reader, int index, out float result)
+ {
+ var item = reader[index];
+ if (item.IsDbNull())
+ {
+ result = default;
+ return false;
+ }
+
+ result = item.ToFloat();
+ return true;
+ }
+
+ public static bool TryGetDouble(this IReadOnlyList reader, int index, out double result)
+ {
+ var item = reader[index];
+ if (item.IsDbNull())
+ {
+ result = default;
+ return false;
+ }
+
+ result = item.ToDouble();
+ return true;
+ }
+
+ public static Guid GetGuid(this IReadOnlyList result, int index)
+ {
+ return result[index].ReadGuidFromBlob();
+ }
+
+ [Conditional("DEBUG")]
+ private static void CheckName(string name)
+ {
+ throw new ArgumentException("Invalid param name: " + name, nameof(name));
+ }
+
+ public static void TryBind(this IStatement statement, string name, double value)
+ {
+ if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
+ {
+ bindParam.Bind(value);
+ }
+ else
+ {
+ CheckName(name);
+ }
+ }
+
+ public static void TryBind(this IStatement statement, string name, string value)
+ {
+ if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
+ {
+ if (value is null)
+ {
+ bindParam.BindNull();
+ }
+ else
+ {
+ bindParam.Bind(value);
+ }
+ }
+ else
+ {
+ CheckName(name);
+ }
+ }
+
+ public static void TryBind(this IStatement statement, string name, bool value)
+ {
+ if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
+ {
+ bindParam.Bind(value);
+ }
+ else
+ {
+ CheckName(name);
+ }
+ }
+
+ public static void TryBind(this IStatement statement, string name, float value)
+ {
+ if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
+ {
+ bindParam.Bind(value);
+ }
+ else
+ {
+ CheckName(name);
+ }
+ }
+
+ public static void TryBind(this IStatement statement, string name, int value)
+ {
+ if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
+ {
+ bindParam.Bind(value);
+ }
+ else
+ {
+ CheckName(name);
+ }
+ }
+
+ public static void TryBind(this IStatement statement, string name, Guid value)
+ {
+ if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
+ {
+ Span byteValue = stackalloc byte[16];
+ value.TryWriteBytes(byteValue);
+ bindParam.Bind(byteValue);
+ }
+ else
+ {
+ CheckName(name);
+ }
+ }
+
+ public static void TryBind(this IStatement statement, string name, DateTime value)
+ {
+ if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
+ {
+ bindParam.Bind(value.ToDateTimeParamValue());
+ }
+ else
+ {
+ CheckName(name);
+ }
+ }
+
+ public static void TryBind(this IStatement statement, string name, long value)
+ {
+ if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
+ {
+ bindParam.Bind(value);
+ }
+ else
+ {
+ CheckName(name);
+ }
+ }
+
+ public static void TryBind(this IStatement statement, string name, ReadOnlySpan value)
+ {
+ if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
+ {
+ bindParam.Bind(value);
+ }
+ else
+ {
+ CheckName(name);
+ }
+ }
+
+ public static void TryBindNull(this IStatement statement, string name)
+ {
+ if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
+ {
+ bindParam.BindNull();
+ }
+ else
+ {
+ CheckName(name);
+ }
+ }
+
+ public static void TryBind(this IStatement statement, string name, DateTime? value)
+ {
+ if (value.HasValue)
+ {
+ TryBind(statement, name, value.Value);
+ }
+ else
+ {
+ TryBindNull(statement, name);
+ }
+ }
+
+ public static void TryBind(this IStatement statement, string name, Guid? value)
+ {
+ if (value.HasValue)
+ {
+ TryBind(statement, name, value.Value);
+ }
+ else
+ {
+ TryBindNull(statement, name);
+ }
+ }
+
+ public static void TryBind(this IStatement statement, string name, double? value)
+ {
+ if (value.HasValue)
+ {
+ TryBind(statement, name, value.Value);
+ }
+ else
+ {
+ TryBindNull(statement, name);
+ }
+ }
+
+ public static void TryBind(this IStatement statement, string name, int? value)
+ {
+ if (value.HasValue)
+ {
+ TryBind(statement, name, value.Value);
+ }
+ else
+ {
+ TryBindNull(statement, name);
+ }
+ }
+
+ public static void TryBind(this IStatement statement, string name, float? value)
+ {
+ if (value.HasValue)
+ {
+ TryBind(statement, name, value.Value);
+ }
+ else
+ {
+ TryBindNull(statement, name);
+ }
+ }
+
+ public static void TryBind(this IStatement statement, string name, bool? value)
+ {
+ if (value.HasValue)
+ {
+ TryBind(statement, name, value.Value);
+ }
+ else
+ {
+ TryBindNull(statement, name);
+ }
+ }
+
+ public static IEnumerable> ExecuteQuery(this IStatement statement)
+ {
+ while (statement.MoveNext())
+ {
+ yield return statement.Current;
+ }
+ }
+ }
+}
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index 146de3ae13..a881e7cb4e 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -47,7 +47,7 @@
-
+
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs
index e8a0af9f88..5f44ba2caf 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using Emby.Server.Implementations.Data;
using Jellyfin.Data.Entities;
+using Jellyfin.Server.Extensions;
using Jellyfin.Server.Implementations;
using MediaBrowser.Controller;
using Microsoft.EntityFrameworkCore;
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs
index 09daae0ff9..a1b87fbe0a 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using Emby.Server.Implementations.Data;
using Jellyfin.Data.Entities.Security;
+using Jellyfin.Server.Extensions;
using Jellyfin.Server.Implementations;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs b/Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs
index 9dee520a50..9f9960a642 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs
@@ -3,6 +3,7 @@ using System.Globalization;
using System.IO;
using Emby.Server.Implementations.Data;
+using Jellyfin.Server.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Globalization;
@@ -91,7 +92,7 @@ namespace Jellyfin.Server.Migrations.Routines
ratingValue = "NULL";
}
- var statement = connection.PrepareStatement("UPDATE TypedBaseItems SET InheritedParentalRatingValue = @Value WHERE OfficialRating = @Rating;");
+ using var statement = connection.PrepareStatement("UPDATE TypedBaseItems SET InheritedParentalRatingValue = @Value WHERE OfficialRating = @Rating;");
statement.TryBind("@Value", ratingValue);
statement.TryBind("@Rating", ratingString);
statement.ExecuteQuery();
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
index 0186500a12..a1de104ade 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
@@ -4,6 +4,7 @@ using Emby.Server.Implementations.Data;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions.Json;
+using Jellyfin.Server.Extensions;
using Jellyfin.Server.Implementations;
using Jellyfin.Server.Implementations.Users;
using MediaBrowser.Controller;