mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-12-22 00:35:26 +03:00
Reenable common PRAGMA setters (#14791)
This commit is contained in:
@@ -0,0 +1,108 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Injects a series of PRAGMA on each connection starts.
|
||||||
|
/// </summary>
|
||||||
|
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<string, string> _customPragma;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="PragmaConnectionInterceptor"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
|
/// <param name="cacheSize">Cache size.</param>
|
||||||
|
/// <param name="lockingMode">Locking mode.</param>
|
||||||
|
/// <param name="journalSizeLimit">Journal Size.</param>
|
||||||
|
/// <param name="tempStoreMode">The https://sqlite.org/pragma.html#pragma_temp_store pragma.</param>
|
||||||
|
/// <param name="syncMode">The https://sqlite.org/pragma.html#pragma_synchronous pragma.</param>
|
||||||
|
/// <param name="customPragma">A list of custom provided Pragma in the list of CustomOptions starting with "#PRAGMA:".</param>
|
||||||
|
public PragmaConnectionInterceptor(ILogger logger, int? cacheSize, string lockingMode, int? journalSizeLimit, int tempStoreMode, int syncMode, IDictionary<string, string> 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; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,10 +42,28 @@ public sealed class SqliteDatabaseProvider : IJellyfinDatabaseProvider
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Initialise(DbContextOptionsBuilder options, DatabaseConfigurationOptions databaseConfiguration)
|
public void Initialise(DbContextOptionsBuilder options, DatabaseConfigurationOptions databaseConfiguration)
|
||||||
{
|
{
|
||||||
|
static T? GetOption<T>(ICollection<CustomDatabaseOption>? options, string key, Func<string, T> converter, Func<T>? defaultValue = null)
|
||||||
|
{
|
||||||
|
if (options is null)
|
||||||
|
{
|
||||||
|
return defaultValue is not null ? defaultValue() : default;
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = options.FirstOrDefault(e => e.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (value is null)
|
||||||
|
{
|
||||||
|
return defaultValue is not null ? defaultValue() : default;
|
||||||
|
}
|
||||||
|
|
||||||
|
return converter(value.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var customOptions = databaseConfiguration.CustomProviderOptions?.Options;
|
||||||
|
|
||||||
var sqliteConnectionBuilder = new SqliteConnectionStringBuilder();
|
var sqliteConnectionBuilder = new SqliteConnectionStringBuilder();
|
||||||
sqliteConnectionBuilder.DataSource = Path.Combine(_applicationPaths.DataPath, "jellyfin.db");
|
sqliteConnectionBuilder.DataSource = Path.Combine(_applicationPaths.DataPath, "jellyfin.db");
|
||||||
sqliteConnectionBuilder.Cache = Enum.Parse<SqliteCacheMode>(databaseConfiguration.CustomProviderOptions?.Options.FirstOrDefault(e => e.Key.Equals("cache", StringComparison.OrdinalIgnoreCase))?.Value ?? nameof(SqliteCacheMode.Default));
|
sqliteConnectionBuilder.Cache = GetOption(customOptions, "cache", Enum.Parse<SqliteCacheMode>, () => SqliteCacheMode.Default);
|
||||||
sqliteConnectionBuilder.Pooling = (databaseConfiguration.CustomProviderOptions?.Options.FirstOrDefault(e => e.Key.Equals("pooling", StringComparison.OrdinalIgnoreCase))?.Value ?? bool.TrueString).Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase);
|
sqliteConnectionBuilder.Pooling = GetOption(customOptions, "pooling", e => e.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase), () => true);
|
||||||
|
|
||||||
var connectionString = sqliteConnectionBuilder.ToString();
|
var connectionString = sqliteConnectionBuilder.ToString();
|
||||||
|
|
||||||
@@ -58,10 +76,18 @@ public sealed class SqliteDatabaseProvider : IJellyfinDatabaseProvider
|
|||||||
sqLiteOptions => sqLiteOptions.MigrationsAssembly(GetType().Assembly))
|
sqLiteOptions => sqLiteOptions.MigrationsAssembly(GetType().Assembly))
|
||||||
// TODO: Remove when https://github.com/dotnet/efcore/pull/35873 is merged & released
|
// TODO: Remove when https://github.com/dotnet/efcore/pull/35873 is merged & released
|
||||||
.ConfigureWarnings(warnings =>
|
.ConfigureWarnings(warnings =>
|
||||||
warnings.Ignore(RelationalEventId.NonTransactionalMigrationOperationWarning));
|
warnings.Ignore(RelationalEventId.NonTransactionalMigrationOperationWarning))
|
||||||
|
.AddInterceptors(new PragmaConnectionInterceptor(
|
||||||
|
_logger,
|
||||||
|
GetOption<int?>(customOptions, "cacheSize", e => int.Parse(e, CultureInfo.InvariantCulture)),
|
||||||
|
GetOption(customOptions, "lockingmode", e => e, () => "NORMAL")!,
|
||||||
|
GetOption(customOptions, "journalsizelimit", int.Parse, () => 134_217_728),
|
||||||
|
GetOption(customOptions, "tempstoremode", int.Parse, () => 2),
|
||||||
|
GetOption(customOptions, "syncmode", int.Parse, () => 1),
|
||||||
|
customOptions?.Where(e => e.Key.StartsWith("#PRAGMA:", StringComparison.OrdinalIgnoreCase)).ToDictionary(e => e.Key["#PRAGMA:".Length..], e => e.Value) ?? []));
|
||||||
|
|
||||||
var enableSensitiveDataLoggingOption = databaseConfiguration.CustomProviderOptions?.Options.FirstOrDefault(e => e.Key.Equals("EnableSensitiveDataLogging", StringComparison.OrdinalIgnoreCase))?.Value;
|
var enableSensitiveDataLogging = GetOption(customOptions, "EnableSensitiveDataLogging", e => e.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase), () => false);
|
||||||
if (!string.IsNullOrEmpty(enableSensitiveDataLoggingOption) && bool.TryParse(enableSensitiveDataLoggingOption, out bool enableSensitiveDataLogging) && enableSensitiveDataLogging)
|
if (enableSensitiveDataLogging)
|
||||||
{
|
{
|
||||||
options.EnableSensitiveDataLogging(enableSensitiveDataLogging);
|
options.EnableSensitiveDataLogging(enableSensitiveDataLogging);
|
||||||
_logger.LogInformation("EnableSensitiveDataLogging is enabled on SQLite connection");
|
_logger.LogInformation("EnableSensitiveDataLogging is enabled on SQLite connection");
|
||||||
|
|||||||
Reference in New Issue
Block a user