using System.Collections.Generic; using System.Data.Common; using System.Globalization; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Logging; namespace Jellyfin.Database.Providers.Sqlite; /// /// Injects a series of PRAGMA on each connection starts. /// public class PragmaConnectionInterceptor : DbConnectionInterceptor { private readonly ILogger _logger; private readonly int? _cacheSize; private readonly string _lockingMode; private readonly int? _journalSizeLimit; private readonly int _tempStoreMode; private readonly int _syncMode; private readonly IDictionary _customPragma; /// /// Initializes a new instance of the class. /// /// The logger. /// Cache size. /// Locking mode. /// Journal Size. /// The https://sqlite.org/pragma.html#pragma_temp_store pragma. /// The https://sqlite.org/pragma.html#pragma_synchronous pragma. /// A list of custom provided Pragma in the list of CustomOptions starting with "#PRAGMA:". public PragmaConnectionInterceptor(ILogger logger, int? cacheSize, string lockingMode, int? journalSizeLimit, int tempStoreMode, int syncMode, IDictionary customPragma) { _logger = logger; _cacheSize = cacheSize; _lockingMode = lockingMode; _journalSizeLimit = journalSizeLimit; _tempStoreMode = tempStoreMode; _syncMode = syncMode; _customPragma = customPragma; InitialCommand = BuildCommandText(); _logger.LogInformation("SQLITE connection pragma command set to: \r\n{PragmaCommand}", InitialCommand); } private string? InitialCommand { get; set; } /// public override void ConnectionOpened(DbConnection connection, ConnectionEndEventData eventData) { base.ConnectionOpened(connection, eventData); using (var command = connection.CreateCommand()) { #pragma warning disable CA2100 // Review SQL queries for security vulnerabilities command.CommandText = InitialCommand; #pragma warning restore CA2100 // Review SQL queries for security vulnerabilities command.ExecuteNonQuery(); } } /// public override async Task ConnectionOpenedAsync(DbConnection connection, ConnectionEndEventData eventData, CancellationToken cancellationToken = default) { await base.ConnectionOpenedAsync(connection, eventData, cancellationToken).ConfigureAwait(false); var command = connection.CreateCommand(); await using (command.ConfigureAwait(false)) { #pragma warning disable CA2100 // Review SQL queries for security vulnerabilities command.CommandText = InitialCommand; #pragma warning restore CA2100 // Review SQL queries for security vulnerabilities await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false); } } private string BuildCommandText() { var sb = new StringBuilder(); if (_cacheSize.HasValue) { sb.AppendLine(CultureInfo.InvariantCulture, $"PRAGMA cache_size={_cacheSize.Value};"); } if (!string.IsNullOrWhiteSpace(_lockingMode)) { sb.AppendLine(CultureInfo.InvariantCulture, $"PRAGMA locking_mode={_lockingMode};"); } if (_journalSizeLimit.HasValue) { sb.AppendLine(CultureInfo.InvariantCulture, $"PRAGMA journal_size_limit={_journalSizeLimit};"); } sb.AppendLine(CultureInfo.InvariantCulture, $"PRAGMA synchronous={_syncMode};"); sb.AppendLine(CultureInfo.InvariantCulture, $"PRAGMA temp_store={_tempStoreMode};"); foreach (var item in _customPragma) { sb.AppendLine(CultureInfo.InvariantCulture, $"PRAGMA {item.Key}={item.Value};"); } return sb.ToString(); } }