New: Postgres Support

pull/654/head
Qstick 3 years ago
parent a61d4ab88c
commit df764ce8b4

@ -20,10 +20,11 @@ class About extends Component {
packageVersion, packageVersion,
packageAuthor, packageAuthor,
isNetCore, isNetCore,
isMono,
isDocker, isDocker,
runtimeVersion, runtimeVersion,
migrationVersion, migrationVersion,
databaseVersion,
databaseType,
appData, appData,
startupPath, startupPath,
mode, mode,
@ -48,14 +49,6 @@ class About extends Component {
/> />
} }
{
isMono &&
<DescriptionListItem
title={translate('MonoVersion')}
data={runtimeVersion}
/>
}
{ {
isNetCore && isNetCore &&
<DescriptionListItem <DescriptionListItem
@ -77,6 +70,11 @@ class About extends Component {
data={migrationVersion} data={migrationVersion}
/> />
<DescriptionListItem
title={translate('Database')}
data={`${titleCase(databaseType)} ${databaseVersion}`}
/>
<DescriptionListItem <DescriptionListItem
title={translate('AppDataDirectory')} title={translate('AppDataDirectory')}
data={appData} data={appData}
@ -114,9 +112,10 @@ About.propTypes = {
packageVersion: PropTypes.string, packageVersion: PropTypes.string,
packageAuthor: PropTypes.string, packageAuthor: PropTypes.string,
isNetCore: PropTypes.bool.isRequired, isNetCore: PropTypes.bool.isRequired,
isMono: PropTypes.bool.isRequired,
runtimeVersion: PropTypes.string.isRequired, runtimeVersion: PropTypes.string.isRequired,
isDocker: PropTypes.bool.isRequired, isDocker: PropTypes.bool.isRequired,
databaseType: PropTypes.string.isRequired,
databaseVersion: PropTypes.string.isRequired,
migrationVersion: PropTypes.number.isRequired, migrationVersion: PropTypes.number.isRequired,
appData: PropTypes.string.isRequired, appData: PropTypes.string.isRequired,
startupPath: PropTypes.string.isRequired, startupPath: PropTypes.string.isRequired,

@ -47,6 +47,12 @@ namespace NzbDrone.Core.Configuration
string UpdateScriptPath { get; } string UpdateScriptPath { get; }
string SyslogServer { get; } string SyslogServer { get; }
int SyslogPort { get; } int SyslogPort { get; }
string PostgresHost { get; }
int PostgresPort { get; }
string PostgresUser { get; }
string PostgresPassword { get; }
string PostgresMainDb { get; }
string PostgresLogDb { get; }
} }
public class ConfigFileProvider : IConfigFileProvider public class ConfigFileProvider : IConfigFileProvider
@ -186,6 +192,12 @@ namespace NzbDrone.Core.Configuration
public string LogLevel => GetValue("LogLevel", "info").ToLowerInvariant(); public string LogLevel => GetValue("LogLevel", "info").ToLowerInvariant();
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false); public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
public string PostgresHost => GetValue("PostgresHost", string.Empty, persist: false);
public string PostgresUser => GetValue("PostgresUser", string.Empty, persist: false);
public string PostgresPassword => GetValue("PostgresPassword", string.Empty, persist: false);
public string PostgresMainDb => GetValue("PostgresMainDb", "prowlarr-main", persist: false);
public string PostgresLogDb => GetValue("PostgresLogDb", "prowlarr-log", persist: false);
public int PostgresPort => GetValueInt("PostgresPort", 5436, persist: false);
public bool LogSql => GetValueBoolean("LogSql", false, persist: false); public bool LogSql => GetValueBoolean("LogSql", false, persist: false);
public int LogRotate => GetValueInt("LogRotate", 50, persist: false); public int LogRotate => GetValueInt("LogRotate", 50, persist: false);
public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false); public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false);

@ -78,7 +78,7 @@ namespace NzbDrone.Core.Datastore
{ {
using (var conn = _database.OpenConnection()) using (var conn = _database.OpenConnection())
{ {
return conn.ExecuteScalar<int>($"SELECT COUNT(*) FROM {_table}"); return conn.ExecuteScalar<int>($"SELECT COUNT(*) FROM \"{_table}\"");
} }
} }
@ -167,14 +167,22 @@ namespace NzbDrone.Core.Datastore
} }
} }
return $"INSERT INTO {_table} ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}); SELECT last_insert_rowid() id"; if (_database.DatabaseType == DatabaseType.SQLite)
{
return $"INSERT INTO {_table} ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}); SELECT last_insert_rowid() id";
}
else
{
return $"INSERT INTO \"{_table}\" ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}) RETURNING \"Id\"";
}
} }
private TModel Insert(IDbConnection connection, IDbTransaction transaction, TModel model) private TModel Insert(IDbConnection connection, IDbTransaction transaction, TModel model)
{ {
SqlBuilderExtensions.LogQuery(_insertSql, model); SqlBuilderExtensions.LogQuery(_insertSql, model);
var multi = connection.QueryMultiple(_insertSql, model, transaction); var multi = connection.QueryMultiple(_insertSql, model, transaction);
var id = (int)multi.Read().First().id; var multiRead = multi.Read();
var id = (int)(multiRead.First().id ?? multiRead.First().Id);
_keyProperty.SetValue(model, id); _keyProperty.SetValue(model, id);
_database.ApplyLazyLoad(model); _database.ApplyLazyLoad(model);
@ -287,7 +295,7 @@ namespace NzbDrone.Core.Datastore
{ {
using (var conn = _database.OpenConnection()) using (var conn = _database.OpenConnection())
{ {
conn.Execute($"DELETE FROM [{_table}]"); conn.Execute($"DELETE FROM \"{_table}\"");
} }
if (vacuum) if (vacuum)
@ -346,7 +354,7 @@ namespace NzbDrone.Core.Datastore
private string GetUpdateSql(List<PropertyInfo> propertiesToUpdate) private string GetUpdateSql(List<PropertyInfo> propertiesToUpdate)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.AppendFormat("UPDATE {0} SET ", _table); sb.AppendFormat("UPDATE \"{0}\" SET ", _table);
for (var i = 0; i < propertiesToUpdate.Count; i++) for (var i = 0; i < propertiesToUpdate.Count; i++)
{ {
@ -414,9 +422,12 @@ namespace NzbDrone.Core.Datastore
pagingSpec.SortKey = $"{_table}.{_keyProperty.Name}"; pagingSpec.SortKey = $"{_table}.{_keyProperty.Name}";
} }
var sortKey = TableMapping.Mapper.GetSortKey(pagingSpec.SortKey);
var sortDirection = pagingSpec.SortDirection == SortDirection.Descending ? "DESC" : "ASC"; var sortDirection = pagingSpec.SortDirection == SortDirection.Descending ? "DESC" : "ASC";
var pagingOffset = (pagingSpec.Page - 1) * pagingSpec.PageSize;
builder.OrderBy($"{pagingSpec.SortKey} {sortDirection} LIMIT {pagingSpec.PageSize} OFFSET {pagingOffset}"); var pagingOffset = Math.Max(pagingSpec.Page - 1, 0) * pagingSpec.PageSize;
builder.OrderBy($"\"{sortKey}\" {sortDirection} LIMIT {pagingSpec.PageSize} OFFSET {pagingOffset}");
return queryFunc(builder).ToList(); return queryFunc(builder).ToList();
} }

@ -1,7 +1,9 @@
using System; using System;
using System.Data.SQLite; using System.Data.SQLite;
using Npgsql;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.Datastore namespace NzbDrone.Core.Datastore
{ {
@ -14,10 +16,17 @@ namespace NzbDrone.Core.Datastore
public class ConnectionStringFactory : IConnectionStringFactory public class ConnectionStringFactory : IConnectionStringFactory
{ {
public ConnectionStringFactory(IAppFolderInfo appFolderInfo) private readonly IConfigFileProvider _configFileProvider;
public ConnectionStringFactory(IAppFolderInfo appFolderInfo, IConfigFileProvider configFileProvider)
{ {
MainDbConnectionString = GetConnectionString(appFolderInfo.GetDatabase()); _configFileProvider = configFileProvider;
LogDbConnectionString = GetConnectionString(appFolderInfo.GetLogDatabase());
MainDbConnectionString = _configFileProvider.PostgresHost.IsNotNullOrWhiteSpace() ? GetPostgresConnectionString(_configFileProvider.PostgresMainDb) :
GetConnectionString(appFolderInfo.GetDatabase());
LogDbConnectionString = _configFileProvider.PostgresHost.IsNotNullOrWhiteSpace() ? GetPostgresConnectionString(_configFileProvider.PostgresLogDb) :
GetConnectionString(appFolderInfo.GetLogDatabase());
} }
public string MainDbConnectionString { get; private set; } public string MainDbConnectionString { get; private set; }
@ -48,5 +57,19 @@ namespace NzbDrone.Core.Datastore
return connectionBuilder.ConnectionString; return connectionBuilder.ConnectionString;
} }
private string GetPostgresConnectionString(string dbName)
{
var connectionBuilder = new NpgsqlConnectionStringBuilder();
connectionBuilder.Database = dbName;
connectionBuilder.Host = _configFileProvider.PostgresHost;
connectionBuilder.Username = _configFileProvider.PostgresUser;
connectionBuilder.Password = _configFileProvider.PostgresPassword;
connectionBuilder.Port = _configFileProvider.PostgresPort;
connectionBuilder.Enlist = false;
return connectionBuilder.ConnectionString;
}
} }
} }

@ -1,5 +1,6 @@
using System; using System;
using System.Data; using System.Data;
using System.Text.RegularExpressions;
using Dapper; using Dapper;
using NLog; using NLog;
using NzbDrone.Common.Instrumentation; using NzbDrone.Common.Instrumentation;
@ -11,6 +12,7 @@ namespace NzbDrone.Core.Datastore
IDbConnection OpenConnection(); IDbConnection OpenConnection();
Version Version { get; } Version Version { get; }
int Migration { get; } int Migration { get; }
DatabaseType DatabaseType { get; }
void Vacuum(); void Vacuum();
} }
@ -32,13 +34,44 @@ namespace NzbDrone.Core.Datastore
return _datamapperFactory(); return _datamapperFactory();
} }
public DatabaseType DatabaseType
{
get
{
using (var db = _datamapperFactory())
{
if (db.ConnectionString.Contains(".db"))
{
return DatabaseType.SQLite;
}
else
{
return DatabaseType.PostgreSQL;
}
}
}
}
public Version Version public Version Version
{ {
get get
{ {
using (var db = _datamapperFactory()) using (var db = _datamapperFactory())
{ {
var version = db.QueryFirstOrDefault<string>("SELECT sqlite_version()"); string version;
try
{
version = db.QueryFirstOrDefault<string>("SHOW server_version");
//Postgres can return extra info about operating system on version call, ignore this
version = Regex.Replace(version, @"\(.*?\)", "");
}
catch
{
version = db.QueryFirstOrDefault<string>("SELECT sqlite_version()");
}
return new Version(version); return new Version(version);
} }
} }
@ -50,7 +83,7 @@ namespace NzbDrone.Core.Datastore
{ {
using (var db = _datamapperFactory()) using (var db = _datamapperFactory())
{ {
return db.QueryFirstOrDefault<int>("SELECT version from VersionInfo ORDER BY version DESC LIMIT 1"); return db.QueryFirstOrDefault<int>("SELECT \"Version\" from \"VersionInfo\" ORDER BY \"Version\" DESC LIMIT 1");
} }
} }
} }
@ -73,4 +106,10 @@ namespace NzbDrone.Core.Datastore
} }
} }
} }
public enum DatabaseType
{
SQLite,
PostgreSQL
}
} }

@ -1,10 +1,13 @@
using System; using System;
using System.Data.Common;
using System.Data.SQLite; using System.Data.SQLite;
using NLog; using NLog;
using Npgsql;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Exceptions; using NzbDrone.Common.Exceptions;
using NzbDrone.Common.Instrumentation; using NzbDrone.Common.Instrumentation;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore.Migration.Framework; using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore namespace NzbDrone.Core.Datastore
@ -85,10 +88,19 @@ namespace NzbDrone.Core.Datastore
var db = new Database(migrationContext.MigrationType.ToString(), () => var db = new Database(migrationContext.MigrationType.ToString(), () =>
{ {
var conn = SQLiteFactory.Instance.CreateConnection(); DbConnection conn;
conn.ConnectionString = connectionString;
conn.Open();
if (connectionString.Contains(".db"))
{
conn = SQLiteFactory.Instance.CreateConnection();
conn.ConnectionString = connectionString;
}
else
{
conn = new NpgsqlConnection(connectionString);
}
conn.Open();
return conn; return conn;
}); });

@ -20,7 +20,7 @@ namespace NzbDrone.Core.Datastore
public static SqlBuilder Select(this SqlBuilder builder, params Type[] types) public static SqlBuilder Select(this SqlBuilder builder, params Type[] types)
{ {
return builder.Select(types.Select(x => TableMapping.Mapper.TableNameMapping(x) + ".*").Join(", ")); return builder.Select(types.Select(x => $"\"{TableMapping.Mapper.TableNameMapping(x)}\".*").Join(", "));
} }
public static SqlBuilder SelectDistinct(this SqlBuilder builder, params Type[] types) public static SqlBuilder SelectDistinct(this SqlBuilder builder, params Type[] types)

@ -1,4 +1,4 @@
using System; using System;
using System.Data; using System.Data;
namespace NzbDrone.Core.Datastore namespace NzbDrone.Core.Datastore
@ -10,10 +10,12 @@ namespace NzbDrone.Core.Datastore
public class LogDatabase : ILogDatabase public class LogDatabase : ILogDatabase
{ {
private readonly IDatabase _database; private readonly IDatabase _database;
private readonly DatabaseType _databaseType;
public LogDatabase(IDatabase database) public LogDatabase(IDatabase database)
{ {
_database = database; _database = database;
_databaseType = _database == null ? DatabaseType.SQLite : _database.DatabaseType;
} }
public IDbConnection OpenConnection() public IDbConnection OpenConnection()
@ -25,6 +27,8 @@ namespace NzbDrone.Core.Datastore
public int Migration => _database.Migration; public int Migration => _database.Migration;
public DatabaseType DatabaseType => _databaseType;
public void Vacuum() public void Vacuum()
{ {
_database.Vacuum(); _database.Vacuum();

@ -1,4 +1,4 @@
using System; using System;
using System.Data; using System.Data;
namespace NzbDrone.Core.Datastore namespace NzbDrone.Core.Datastore
@ -10,10 +10,12 @@ namespace NzbDrone.Core.Datastore
public class MainDatabase : IMainDatabase public class MainDatabase : IMainDatabase
{ {
private readonly IDatabase _database; private readonly IDatabase _database;
private readonly DatabaseType _databaseType;
public MainDatabase(IDatabase database) public MainDatabase(IDatabase database)
{ {
_database = database; _database = database;
_databaseType = _database == null ? DatabaseType.SQLite : _database.DatabaseType;
} }
public IDbConnection OpenConnection() public IDbConnection OpenConnection()
@ -25,6 +27,8 @@ namespace NzbDrone.Core.Datastore
public int Migration => _database.Migration; public int Migration => _database.Migration;
public DatabaseType DatabaseType => _databaseType;
public void Vacuum() public void Vacuum()
{ {
_database.Vacuum(); _database.Vacuum();

@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration
{ {
protected override void MainDbUpgrade() protected override void MainDbUpgrade()
{ {
Execute.Sql("UPDATE Notifications SET Implementation = Replace(Implementation, 'DiscordNotifier', 'Notifiarr'),ConfigContract = Replace(ConfigContract, 'DiscordNotifierSettings', 'NotifiarrSettings') WHERE Implementation = 'DiscordNotifier';"); Execute.Sql("UPDATE \"Notifications\" SET \"Implementation\" = Replace(\"Implementation\", 'DiscordNotifier', 'Notifiarr'),\"ConfigContract\" = Replace(\"ConfigContract\", 'DiscordNotifierSettings', 'NotifiarrSettings') WHERE \"Implementation\" = 'DiscordNotifier';");
} }
} }
} }

@ -11,7 +11,8 @@ namespace NzbDrone.Core.Datastore.Migration
Alter.Table("History") Alter.Table("History")
.AddColumn("Successful").AsBoolean().NotNullable().WithDefaultValue(true); .AddColumn("Successful").AsBoolean().NotNullable().WithDefaultValue(true);
Execute.Sql("UPDATE History SET Successful = (json_extract(History.Data,'$.successful') == 'True' );"); // Postgres added after this, not needed
IfDatabase("sqlite").Execute.Sql("UPDATE \"History\" SET \"Successful\" = (json_extract(\"History\".\"Data\",'$.successful') == 'True' );");
} }
} }
} }

@ -19,7 +19,7 @@ namespace NzbDrone.Core.Datastore.Migration
using (var cmd = conn.CreateCommand()) using (var cmd = conn.CreateCommand())
{ {
cmd.Transaction = tran; cmd.Transaction = tran;
cmd.CommandText = "SELECT Id, Settings FROM Indexers WHERE Implementation = 'Redacted'"; cmd.CommandText = "SELECT \"Id\", \"Settings\" FROM \"Indexers\" WHERE \"Implementation\" = 'Redacted'";
using (var reader = cmd.ExecuteReader()) using (var reader = cmd.ExecuteReader())
{ {
@ -48,7 +48,7 @@ namespace NzbDrone.Core.Datastore.Migration
using (var updateCmd = conn.CreateCommand()) using (var updateCmd = conn.CreateCommand())
{ {
updateCmd.Transaction = tran; updateCmd.Transaction = tran;
updateCmd.CommandText = "UPDATE Indexers SET Settings = ?, ConfigContract = ?, Enable = 0 WHERE Id = ?"; updateCmd.CommandText = "UPDATE \"Indexers\" SET \"Settings\" = ?, \"ConfigContract\" = ?, \"Enable\" = 0 WHERE \"Id\" = ?";
updateCmd.AddParameter(settings); updateCmd.AddParameter(settings);
updateCmd.AddParameter("RedactedSettings"); updateCmd.AddParameter("RedactedSettings");
updateCmd.AddParameter(id); updateCmd.AddParameter(id);

@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration
{ {
protected override void MainDbUpgrade() protected override void MainDbUpgrade()
{ {
Update.Table("Indexers").Set(new { ConfigContract = "Unit3dSettings", Enable = 0 }).Where(new { Implementation = "DesiTorrents" }); Update.Table("Indexers").Set(new { ConfigContract = "Unit3dSettings", Enable = true }).Where(new { Implementation = "DesiTorrents" });
} }
} }
} }

@ -2,6 +2,7 @@ using System;
using System.Diagnostics; using System.Diagnostics;
using System.Reflection; using System.Reflection;
using FluentMigrator.Runner; using FluentMigrator.Runner;
using FluentMigrator.Runner.Generators;
using FluentMigrator.Runner.Initialization; using FluentMigrator.Runner.Initialization;
using FluentMigrator.Runner.Processors; using FluentMigrator.Runner.Processors;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -34,11 +35,16 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
_logger.Info("*** Migrating {0} ***", connectionString); _logger.Info("*** Migrating {0} ***", connectionString);
var serviceProvider = new ServiceCollection() ServiceProvider serviceProvider;
var db = connectionString.Contains(".db") ? "sqlite" : "postgres";
serviceProvider = new ServiceCollection()
.AddLogging(b => b.AddNLog()) .AddLogging(b => b.AddNLog())
.AddFluentMigratorCore() .AddFluentMigratorCore()
.ConfigureRunner( .ConfigureRunner(
builder => builder builder => builder
.AddPostgres()
.AddNzbDroneSQLite() .AddNzbDroneSQLite()
.WithGlobalConnectionString(connectionString) .WithGlobalConnectionString(connectionString)
.WithMigrationsIn(Assembly.GetExecutingAssembly())) .WithMigrationsIn(Assembly.GetExecutingAssembly()))
@ -48,6 +54,14 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
opt.PreviewOnly = false; opt.PreviewOnly = false;
opt.Timeout = TimeSpan.FromSeconds(60); opt.Timeout = TimeSpan.FromSeconds(60);
}) })
.Configure<SelectingProcessorAccessorOptions>(cfg =>
{
cfg.ProcessorId = db;
})
.Configure<SelectingGeneratorAccessorOptions>(cfg =>
{
cfg.GeneratorId = db;
})
.BuildServiceProvider(); .BuildServiceProvider();
using (var scope = serviceProvider.CreateScope()) using (var scope = serviceProvider.CreateScope())

@ -8,6 +8,8 @@ namespace NzbDrone.Core.Datastore
{ {
public class TableMapper public class TableMapper
{ {
private readonly HashSet<string> _allowedOrderBy = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
public TableMapper() public TableMapper()
{ {
IgnoreList = new Dictionary<Type, List<PropertyInfo>>(); IgnoreList = new Dictionary<Type, List<PropertyInfo>>();
@ -27,12 +29,12 @@ namespace NzbDrone.Core.Datastore
if (IgnoreList.TryGetValue(type, out var list)) if (IgnoreList.TryGetValue(type, out var list))
{ {
return new ColumnMapper<TEntity>(list, LazyLoadList[type]); return new ColumnMapper<TEntity>(list, LazyLoadList[type], _allowedOrderBy);
} }
IgnoreList[type] = new List<PropertyInfo>(); IgnoreList[type] = new List<PropertyInfo>();
LazyLoadList[type] = new List<LazyLoadedProperty>(); LazyLoadList[type] = new List<LazyLoadedProperty>();
return new ColumnMapper<TEntity>(IgnoreList[type], LazyLoadList[type]); return new ColumnMapper<TEntity>(IgnoreList[type], LazyLoadList[type], _allowedOrderBy);
} }
public List<PropertyInfo> ExcludeProperties(Type x) public List<PropertyInfo> ExcludeProperties(Type x)
@ -40,6 +42,64 @@ namespace NzbDrone.Core.Datastore
return IgnoreList.ContainsKey(x) ? IgnoreList[x] : new List<PropertyInfo>(); return IgnoreList.ContainsKey(x) ? IgnoreList[x] : new List<PropertyInfo>();
} }
public bool IsValidSortKey(string sortKey)
{
string table = null;
if (sortKey.Contains('.'))
{
var split = sortKey.Split('.');
if (split.Length != 2)
{
return false;
}
table = split[0];
sortKey = split[1];
}
if (table != null && !TableMap.Values.Contains(table, StringComparer.OrdinalIgnoreCase))
{
return false;
}
if (!_allowedOrderBy.Contains(sortKey))
{
return false;
}
return true;
}
public string GetSortKey(string sortKey)
{
string table = null;
if (sortKey.Contains('.'))
{
var split = sortKey.Split('.');
if (split.Length != 2)
{
return sortKey;
}
table = split[0];
sortKey = split[1];
}
if (table != null && !TableMap.Values.Contains(table, StringComparer.OrdinalIgnoreCase))
{
return sortKey;
}
if (!_allowedOrderBy.Contains(sortKey))
{
return sortKey;
}
return _allowedOrderBy.First(x => x.Equals(sortKey, StringComparison.OrdinalIgnoreCase));
}
public string TableNameMapping(Type x) public string TableNameMapping(Type x)
{ {
return TableMap.ContainsKey(x) ? TableMap[x] : null; return TableMap.ContainsKey(x) ? TableMap[x] : null;
@ -47,17 +107,17 @@ namespace NzbDrone.Core.Datastore
public string SelectTemplate(Type x) public string SelectTemplate(Type x)
{ {
return $"SELECT /**select**/ FROM {TableMap[x]} /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/"; return $"SELECT /**select**/ FROM \"{TableMap[x]}\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/";
} }
public string DeleteTemplate(Type x) public string DeleteTemplate(Type x)
{ {
return $"DELETE FROM {TableMap[x]} /**where**/"; return $"DELETE FROM \"{TableMap[x]}\" /**where**/";
} }
public string PageCountTemplate(Type x) public string PageCountTemplate(Type x)
{ {
return $"SELECT /**select**/ FROM {TableMap[x]} /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/"; return $"SELECT /**select**/ FROM \"{TableMap[x]}\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/";
} }
} }
@ -72,17 +132,20 @@ namespace NzbDrone.Core.Datastore
{ {
private readonly List<PropertyInfo> _ignoreList; private readonly List<PropertyInfo> _ignoreList;
private readonly List<LazyLoadedProperty> _lazyLoadList; private readonly List<LazyLoadedProperty> _lazyLoadList;
private readonly HashSet<string> _allowedOrderBy;
public ColumnMapper(List<PropertyInfo> ignoreList, List<LazyLoadedProperty> lazyLoadList) public ColumnMapper(List<PropertyInfo> ignoreList, List<LazyLoadedProperty> lazyLoadList, HashSet<string> allowedOrderBy)
{ {
_ignoreList = ignoreList; _ignoreList = ignoreList;
_lazyLoadList = lazyLoadList; _lazyLoadList = lazyLoadList;
_allowedOrderBy = allowedOrderBy;
} }
public ColumnMapper<T> AutoMapPropertiesWhere(Func<PropertyInfo, bool> predicate) public ColumnMapper<T> AutoMapPropertiesWhere(Func<PropertyInfo, bool> predicate)
{ {
var properties = typeof(T).GetProperties(); var properties = typeof(T).GetProperties();
_ignoreList.AddRange(properties.Where(x => !predicate(x))); _ignoreList.AddRange(properties.Where(x => !predicate(x)));
_allowedOrderBy.UnionWith(properties.Where(x => predicate(x)).Select(x => x.Name));
return this; return this;
} }

@ -1,4 +1,4 @@
using Dapper; using Dapper;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers namespace NzbDrone.Core.Housekeeping.Housekeepers
@ -16,9 +16,9 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
{ {
using (var mapper = _database.OpenConnection()) using (var mapper = _database.OpenConnection())
{ {
mapper.Execute(@"DELETE FROM Users mapper.Execute(@"DELETE FROM ""Users""
WHERE ID NOT IN ( WHERE ""Id"" NOT IN (
SELECT ID FROM Users SELECT ""Id"" FROM ""Users""
LIMIT 1)"); LIMIT 1)");
} }
} }

@ -1,4 +1,4 @@
using Dapper; using Dapper;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers namespace NzbDrone.Core.Housekeeping.Housekeepers
@ -16,12 +16,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
{ {
var mapper = _database.OpenConnection(); var mapper = _database.OpenConnection();
mapper.Execute(@"DELETE FROM DownloadClientStatus mapper.Execute(@"DELETE FROM ""DownloadClientStatus""
WHERE Id IN ( WHERE ""Id"" IN (
SELECT DownloadClientStatus.Id FROM DownloadClientStatus SELECT ""DownloadClientStatus"".""Id"" FROM ""DownloadClientStatus""
LEFT OUTER JOIN DownloadClients LEFT OUTER JOIN ""DownloadClients""
ON DownloadClientStatus.ProviderId = DownloadClients.Id ON ""DownloadClientStatus"".""ProviderId"" = ""DownloadClients"".""Id""
WHERE DownloadClients.Id IS NULL)"); WHERE ""DownloadClients"".""Id"" IS NULL)");
} }
} }
} }

@ -21,12 +21,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
{ {
using (var mapper = _database.OpenConnection()) using (var mapper = _database.OpenConnection())
{ {
mapper.Execute(@"DELETE FROM History mapper.Execute(@"DELETE FROM ""History""
WHERE Id IN ( WHERE ""Id"" IN (
SELECT History.Id FROM History SELECT ""History"".""Id"" FROM ""History""
LEFT OUTER JOIN Indexers LEFT OUTER JOIN ""Indexers""
ON History.IndexerId = Indexers.Id ON ""History"".""IndexerId"" = ""Indexers"".""Id""
WHERE Indexers.Id IS NULL)"); WHERE ""Indexers"".""Id"" IS NULL)");
} }
} }
} }

@ -16,12 +16,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
{ {
using (var mapper = _database.OpenConnection()) using (var mapper = _database.OpenConnection())
{ {
mapper.Execute(@"DELETE FROM IndexerStatus mapper.Execute(@"DELETE FROM ""IndexerStatus""
WHERE Id IN ( WHERE ""Id"" IN (
SELECT IndexerStatus.Id FROM IndexerStatus SELECT ""IndexerStatus"".""Id"" FROM ""IndexerStatus""
LEFT OUTER JOIN Indexers LEFT OUTER JOIN ""Indexers""
ON IndexerStatus.ProviderId = Indexers.Id ON ""IndexerStatus"".""ProviderId"" = ""Indexers"".""Id""
WHERE Indexers.Id IS NULL)"); WHERE ""Indexers"".""Id"" IS NULL)");
} }
} }
} }

@ -24,15 +24,22 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
.Distinct() .Distinct()
.ToArray(); .ToArray();
var usedTagsList = string.Join(",", usedTags.Select(d => d.ToString()).ToArray()); if (usedTags.Length > 0)
{
var usedTagsList = string.Join(",", usedTags.Select(d => d.ToString()).ToArray());
mapper.Execute($"DELETE FROM Tags WHERE NOT Id IN ({usedTagsList})"); mapper.Execute($"DELETE FROM \"Tags\" WHERE NOT \"Id\" IN ({usedTagsList})");
}
else
{
mapper.Execute($"DELETE FROM \"Tags\"");
}
} }
} }
private int[] GetUsedTags(string table, IDbConnection mapper) private int[] GetUsedTags(string table, IDbConnection mapper)
{ {
return mapper.Query<List<int>>($"SELECT DISTINCT Tags FROM {table} WHERE NOT Tags = '[]'") return mapper.Query<List<int>>($"SELECT DISTINCT \"Tags\" FROM \"{table}\" WHERE NOT \"Tags\" = '[]'")
.SelectMany(x => x) .SelectMany(x => x)
.Distinct() .Distinct()
.ToArray(); .ToArray();

@ -1,4 +1,4 @@
using System; using System;
using Dapper; using Dapper;
using NLog; using NLog;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
@ -26,9 +26,9 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
using (var mapper = _database.OpenConnection()) using (var mapper = _database.OpenConnection())
{ {
mapper.Execute(@"UPDATE ScheduledTasks mapper.Execute(@"UPDATE ""ScheduledTasks""
SET LastExecution = @time SET ""LastExecution"" = @time
WHERE LastExecution > @time", WHERE ""LastExecution"" > @time",
new { time = DateTime.UtcNow }); new { time = DateTime.UtcNow });
} }
} }

@ -1,9 +1,11 @@
using System.Data; using System;
using System.Data;
using System.Data.SQLite; using System.Data.SQLite;
using NLog; using NLog;
using NLog.Common; using NLog.Common;
using NLog.Config; using NLog.Config;
using NLog.Targets; using NLog.Targets;
using Npgsql;
using NzbDrone.Common.Instrumentation; using NzbDrone.Common.Instrumentation;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Lifecycle;
@ -13,7 +15,7 @@ namespace NzbDrone.Core.Instrumentation
{ {
public class DatabaseTarget : TargetWithLayout, IHandle<ApplicationShutdownRequested> public class DatabaseTarget : TargetWithLayout, IHandle<ApplicationShutdownRequested>
{ {
private const string INSERT_COMMAND = "INSERT INTO [Logs]([Message],[Time],[Logger],[Exception],[ExceptionType],[Level]) " + private const string INSERT_COMMAND = "INSERT INTO \"Logs\" (\"Message\",\"Time\",\"Logger\",\"Exception\",\"ExceptionType\",\"Level\") " +
"VALUES(@Message,@Time,@Logger,@Exception,@ExceptionType,@Level)"; "VALUES(@Message,@Time,@Logger,@Exception,@ExceptionType,@Level)";
private readonly IConnectionStringFactory _connectionStringFactory; private readonly IConnectionStringFactory _connectionStringFactory;
@ -83,23 +85,16 @@ namespace NzbDrone.Core.Instrumentation
log.Level = logEvent.Level.Name; log.Level = logEvent.Level.Name;
using (var connection = var connectionString = _connectionStringFactory.LogDbConnectionString;
SQLiteFactory.Instance.CreateConnection())
//TODO: Probably need more robust way to differentiate what's being used
if (connectionString.Contains(".db"))
{ {
connection.ConnectionString = _connectionStringFactory.LogDbConnectionString; WriteSqliteLog(log, connectionString);
connection.Open(); }
using (var sqlCommand = connection.CreateCommand()) else
{ {
sqlCommand.CommandText = INSERT_COMMAND; WritePostgresLog(log, connectionString);
sqlCommand.Parameters.Add(new SQLiteParameter("Message", DbType.String) { Value = log.Message });
sqlCommand.Parameters.Add(new SQLiteParameter("Time", DbType.DateTime) { Value = log.Time.ToUniversalTime() });
sqlCommand.Parameters.Add(new SQLiteParameter("Logger", DbType.String) { Value = log.Logger });
sqlCommand.Parameters.Add(new SQLiteParameter("Exception", DbType.String) { Value = log.Exception });
sqlCommand.Parameters.Add(new SQLiteParameter("ExceptionType", DbType.String) { Value = log.ExceptionType });
sqlCommand.Parameters.Add(new SQLiteParameter("Level", DbType.String) { Value = log.Level });
sqlCommand.ExecuteNonQuery();
}
} }
} }
catch (SQLiteException ex) catch (SQLiteException ex)
@ -109,6 +104,48 @@ namespace NzbDrone.Core.Instrumentation
} }
} }
private void WritePostgresLog(Log log, string connectionString)
{
using (var connection =
new NpgsqlConnection(connectionString))
{
connection.Open();
using (var sqlCommand = connection.CreateCommand())
{
sqlCommand.CommandText = INSERT_COMMAND;
sqlCommand.Parameters.Add(new NpgsqlParameter("Message", DbType.String) { Value = log.Message });
sqlCommand.Parameters.Add(new NpgsqlParameter("Time", DbType.DateTime) { Value = log.Time.ToUniversalTime() });
sqlCommand.Parameters.Add(new NpgsqlParameter("Logger", DbType.String) { Value = log.Logger });
sqlCommand.Parameters.Add(new NpgsqlParameter("Exception", DbType.String) { Value = log.Exception == null ? DBNull.Value : log.Exception });
sqlCommand.Parameters.Add(new NpgsqlParameter("ExceptionType", DbType.String) { Value = log.ExceptionType == null ? DBNull.Value : log.ExceptionType });
sqlCommand.Parameters.Add(new NpgsqlParameter("Level", DbType.String) { Value = log.Level });
sqlCommand.ExecuteNonQuery();
}
}
}
private void WriteSqliteLog(Log log, string connectionString)
{
using (var connection =
SQLiteFactory.Instance.CreateConnection())
{
connection.ConnectionString = connectionString;
connection.Open();
using (var sqlCommand = connection.CreateCommand())
{
sqlCommand.CommandText = INSERT_COMMAND;
sqlCommand.Parameters.Add(new SQLiteParameter("Message", DbType.String) { Value = log.Message });
sqlCommand.Parameters.Add(new SQLiteParameter("Time", DbType.DateTime) { Value = log.Time.ToUniversalTime() });
sqlCommand.Parameters.Add(new SQLiteParameter("Logger", DbType.String) { Value = log.Logger });
sqlCommand.Parameters.Add(new SQLiteParameter("Exception", DbType.String) { Value = log.Exception });
sqlCommand.Parameters.Add(new SQLiteParameter("ExceptionType", DbType.String) { Value = log.ExceptionType });
sqlCommand.Parameters.Add(new SQLiteParameter("Level", DbType.String) { Value = log.Level });
sqlCommand.ExecuteNonQuery();
}
}
}
public void Handle(ApplicationShutdownRequested message) public void Handle(ApplicationShutdownRequested message)
{ {
if (LogManager.Configuration?.LoggingRules?.Contains(Rule) == true) if (LogManager.Configuration?.LoggingRules?.Contains(Rule) == true)

@ -31,7 +31,7 @@ namespace NzbDrone.Core.Messaging.Commands
public void OrphanStarted() public void OrphanStarted()
{ {
var sql = @"UPDATE Commands SET Status = @Orphaned, EndedAt = @Ended WHERE Status = @Started"; var sql = @"UPDATE ""Commands"" SET ""Status"" = @Orphaned, ""EndedAt"" = @Ended WHERE ""Status"" = @Started";
var args = new var args = new
{ {
Orphaned = (int)CommandStatus.Orphaned, Orphaned = (int)CommandStatus.Orphaned,

@ -9,9 +9,11 @@
<PackageReference Include="MailKit" Version="2.14.0" /> <PackageReference Include="MailKit" Version="2.14.0" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
<PackageReference Include="NLog.Targets.Syslog" Version="6.0.2" /> <PackageReference Include="NLog.Targets.Syslog" Version="6.0.2" />
<PackageReference Include="Npgsql" Version="5.0.11" />
<PackageReference Include="System.Memory" Version="4.5.4" /> <PackageReference Include="System.Memory" Version="4.5.4" />
<PackageReference Include="System.ServiceModel.Syndication" Version="6.0.0" /> <PackageReference Include="System.ServiceModel.Syndication" Version="6.0.0" />
<PackageReference Include="FluentMigrator.Runner.SQLite" Version="3.3.1" /> <PackageReference Include="FluentMigrator.Runner.SQLite" Version="3.3.1" />
<PackageReference Include="FluentMigrator.Runner.Postgres" Version="3.3.1" />
<PackageReference Include="FluentValidation" Version="8.6.2" /> <PackageReference Include="FluentValidation" Version="8.6.2" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NLog" Version="4.7.9" /> <PackageReference Include="NLog" Version="4.7.9" />

@ -77,7 +77,8 @@ namespace Prowlarr.Api.V1.System
Mode = _runtimeInfo.Mode, Mode = _runtimeInfo.Mode,
Branch = _configFileProvider.Branch, Branch = _configFileProvider.Branch,
Authentication = _configFileProvider.AuthenticationMethod, Authentication = _configFileProvider.AuthenticationMethod,
SqliteVersion = _database.Version, DatabaseType = _database.DatabaseType,
DatabaseVersion = _database.Version,
MigrationVersion = _database.Migration, MigrationVersion = _database.Migration,
UrlBase = _configFileProvider.UrlBase, UrlBase = _configFileProvider.UrlBase,
RuntimeVersion = _platformInfo.Version, RuntimeVersion = _platformInfo.Version,

Loading…
Cancel
Save