feat: upgrade to .NET 6, refactor everything

This commit is contained in:
spiral
2021-11-26 21:10:56 -05:00
parent d28e99ba43
commit 1918c56937
314 changed files with 27954 additions and 27966 deletions

View File

@@ -1,19 +1,17 @@
using System.Collections.Generic;
using System.Data.Common;
using Dapper;
namespace PluralKit.Core
{
public static class ConnectionUtils
{
public static async IAsyncEnumerable<T> QueryStreamAsync<T>(this IPKConnection conn, string sql, object param)
{
await using var reader = (DbDataReader)await conn.ExecuteReaderAsync(sql, param);
var parser = reader.GetRowParser<T>();
namespace PluralKit.Core;
while (await reader.ReadAsync())
yield return parser(reader);
}
public static class ConnectionUtils
{
public static async IAsyncEnumerable<T> QueryStreamAsync<T>(this IPKConnection conn, string sql, object param)
{
await using var reader = (DbDataReader)await conn.ExecuteReaderAsync(sql, param);
var parser = reader.GetRowParser<T>();
while (await reader.ReadAsync())
yield return parser(reader);
}
}

View File

@@ -1,85 +1,81 @@
using System;
using System.Data;
using System.IO;
using System.Threading.Tasks;
using Dapper;
using Serilog;
namespace PluralKit.Core
namespace PluralKit.Core;
internal class DatabaseMigrator
{
internal class DatabaseMigrator
private const string RootPath = "PluralKit.Core.Database"; // "resource path" root for SQL files
private const int TargetSchemaVersion = 20;
private readonly ILogger _logger;
public DatabaseMigrator(ILogger logger)
{
private const string RootPath = "PluralKit.Core.Database"; // "resource path" root for SQL files
private const int TargetSchemaVersion = 20;
private readonly ILogger _logger;
_logger = logger;
}
public DatabaseMigrator(ILogger logger)
public async Task ApplyMigrations(IPKConnection conn)
{
// Run everything in a transaction
await using var tx = await conn.BeginTransactionAsync();
// Before applying migrations, clean out views/functions to prevent type errors
await ExecuteSqlFile($"{RootPath}.clean.sql", conn, tx);
// Apply all migrations between the current database version and the target version
await ApplyMigrations(conn, tx);
// Now, reapply views/functions (we deleted them above, no need to worry about conflicts)
await ExecuteSqlFile($"{RootPath}.Views.views.sql", conn, tx);
await ExecuteSqlFile($"{RootPath}.Functions.functions.sql", conn, tx);
// Finally, commit tx
await tx.CommitAsync();
}
private async Task ApplyMigrations(IPKConnection conn, IDbTransaction tx)
{
var currentVersion = await GetCurrentDatabaseVersion(conn);
_logger.Information("Current schema version: {CurrentVersion}", currentVersion);
for (var migration = currentVersion + 1; migration <= TargetSchemaVersion; migration++)
{
_logger = logger;
}
public async Task ApplyMigrations(IPKConnection conn)
{
// Run everything in a transaction
await using var tx = await conn.BeginTransactionAsync();
// Before applying migrations, clean out views/functions to prevent type errors
await ExecuteSqlFile($"{RootPath}.clean.sql", conn, tx);
// Apply all migrations between the current database version and the target version
await ApplyMigrations(conn, tx);
// Now, reapply views/functions (we deleted them above, no need to worry about conflicts)
await ExecuteSqlFile($"{RootPath}.Views.views.sql", conn, tx);
await ExecuteSqlFile($"{RootPath}.Functions.functions.sql", conn, tx);
// Finally, commit tx
await tx.CommitAsync();
}
private async Task ApplyMigrations(IPKConnection conn, IDbTransaction tx)
{
var currentVersion = await GetCurrentDatabaseVersion(conn);
_logger.Information("Current schema version: {CurrentVersion}", currentVersion);
for (var migration = currentVersion + 1; migration <= TargetSchemaVersion; migration++)
{
_logger.Information("Applying schema migration {MigrationId}", migration);
await ExecuteSqlFile($"{RootPath}.Migrations.{migration}.sql", conn, tx);
}
}
private async Task ExecuteSqlFile(string resourceName, IPKConnection conn, IDbTransaction tx = null)
{
await using var stream = typeof(Database).Assembly.GetManifestResourceStream(resourceName);
if (stream == null) throw new ArgumentException($"Invalid resource name '{resourceName}'");
using var reader = new StreamReader(stream);
var query = await reader.ReadToEndAsync();
await conn.ExecuteAsync(query, transaction: tx);
// If the above creates new enum/composite types, we must tell Npgsql to reload the internal type caches
// This will propagate to every other connection as well, since it marks the global type mapper collection dirty.
((PKConnection)conn).ReloadTypes();
}
private async Task<int> GetCurrentDatabaseVersion(IPKConnection conn)
{
// First, check if the "info" table exists (it may not, if this is a *really* old database)
var hasInfoTable =
await conn.QuerySingleOrDefaultAsync<int>(
"select count(*) from information_schema.tables where table_name = 'info'") == 1;
// If we have the table, read the schema version
if (hasInfoTable)
return await conn.QuerySingleOrDefaultAsync<int>("select schema_version from info");
// If not, we return version "-1"
// This means migration 0 will get executed, getting us into a consistent state
// Then, migration 1 gets executed, which creates the info table and sets version to 1
return -1;
_logger.Information("Applying schema migration {MigrationId}", migration);
await ExecuteSqlFile($"{RootPath}.Migrations.{migration}.sql", conn, tx);
}
}
private async Task ExecuteSqlFile(string resourceName, IPKConnection conn, IDbTransaction tx = null)
{
await using var stream = typeof(Database).Assembly.GetManifestResourceStream(resourceName);
if (stream == null) throw new ArgumentException($"Invalid resource name '{resourceName}'");
using var reader = new StreamReader(stream);
var query = await reader.ReadToEndAsync();
await conn.ExecuteAsync(query, transaction: tx);
// If the above creates new enum/composite types, we must tell Npgsql to reload the internal type caches
// This will propagate to every other connection as well, since it marks the global type mapper collection dirty.
((PKConnection)conn).ReloadTypes();
}
private async Task<int> GetCurrentDatabaseVersion(IPKConnection conn)
{
// First, check if the "info" table exists (it may not, if this is a *really* old database)
var hasInfoTable =
await conn.QuerySingleOrDefaultAsync<int>(
"select count(*) from information_schema.tables where table_name = 'info'") == 1;
// If we have the table, read the schema version
if (hasInfoTable)
return await conn.QuerySingleOrDefaultAsync<int>("select schema_version from info");
// If not, we return version "-1"
// This means migration 0 will get executed, getting us into a consistent state
// Then, migration 1 gets executed, which creates the info table and sets version to 1
return -1;
}
}

View File

@@ -1,20 +1,17 @@
using System.Threading;
namespace PluralKit.Core;
namespace PluralKit.Core
public class DbConnectionCountHolder
{
public class DbConnectionCountHolder
private int _connectionCount;
public int ConnectionCount => _connectionCount;
public void Increment()
{
private int _connectionCount;
public int ConnectionCount => _connectionCount;
Interlocked.Increment(ref _connectionCount);
}
public void Increment()
{
Interlocked.Increment(ref _connectionCount);
}
public void Decrement()
{
Interlocked.Decrement(ref _connectionCount);
}
public void Decrement()
{
Interlocked.Decrement(ref _connectionCount);
}
}

View File

@@ -1,28 +1,24 @@
using System;
using System.Collections.Generic;
using SqlKata;
namespace PluralKit.Core
namespace PluralKit.Core;
internal class QueryPatchWrapper
{
internal class QueryPatchWrapper
private readonly Dictionary<string, object> _dict = new();
public QueryPatchWrapper With<T>(string columnName, Partial<T> partialValue)
{
private Dictionary<string, object> _dict = new();
if (partialValue.IsPresent)
_dict.Add(columnName, partialValue);
public QueryPatchWrapper With<T>(string columnName, Partial<T> partialValue)
{
if (partialValue.IsPresent)
_dict.Add(columnName, partialValue);
return this;
}
public Query ToQuery(Query q) => q.AsUpdate(_dict);
return this;
}
internal static class SqlKataExtensions
{
internal static Query ApplyPatch(this Query query, Func<QueryPatchWrapper, QueryPatchWrapper> func)
=> func(new QueryPatchWrapper()).ToQuery(query);
}
public Query ToQuery(Query q) => q.AsUpdate(_dict);
}
internal static class SqlKataExtensions
{
internal static Query ApplyPatch(this Query query, Func<QueryPatchWrapper, QueryPatchWrapper> func)
=> func(new QueryPatchWrapper()).ToQuery(query);
}

View File

@@ -1,45 +1,41 @@
using System.Text;
using Dapper;
namespace PluralKit.Core
namespace PluralKit.Core;
public class UpdateQueryBuilder
{
public class UpdateQueryBuilder
private readonly DynamicParameters _params = new();
private readonly QueryBuilder _qb;
private UpdateQueryBuilder(QueryBuilder qb)
{
private readonly QueryBuilder _qb;
private readonly DynamicParameters _params = new DynamicParameters();
private UpdateQueryBuilder(QueryBuilder qb)
{
_qb = qb;
}
public static UpdateQueryBuilder Insert(string table) => new UpdateQueryBuilder(QueryBuilder.Insert(table));
public static UpdateQueryBuilder Update(string table, string condition) => new UpdateQueryBuilder(QueryBuilder.Update(table, condition));
public static UpdateQueryBuilder Upsert(string table, string conflictField) => new UpdateQueryBuilder(QueryBuilder.Upsert(table, conflictField));
public UpdateQueryBuilder WithConstant<T>(string name, T value)
{
_params.Add(name, value);
_qb.Constant(name, $"@{name}");
return this;
}
public UpdateQueryBuilder With<T>(string columnName, T value)
{
_params.Add(columnName, value);
_qb.Variable(columnName, $"@{columnName}");
return this;
}
public UpdateQueryBuilder With<T>(string columnName, Partial<T> partialValue)
{
return partialValue.IsPresent ? With(columnName, partialValue.Value) : this;
}
public (string Query, DynamicParameters Parameters) Build(string suffix = "")
{
return (_qb.Build(suffix), _params);
}
_qb = qb;
}
public static UpdateQueryBuilder Insert(string table) => new(QueryBuilder.Insert(table));
public static UpdateQueryBuilder Update(string table, string condition) =>
new(QueryBuilder.Update(table, condition));
public static UpdateQueryBuilder Upsert(string table, string conflictField) =>
new(QueryBuilder.Upsert(table, conflictField));
public UpdateQueryBuilder WithConstant<T>(string name, T value)
{
_params.Add(name, value);
_qb.Constant(name, $"@{name}");
return this;
}
public UpdateQueryBuilder With<T>(string columnName, T value)
{
_params.Add(columnName, value);
_qb.Variable(columnName, $"@{columnName}");
return this;
}
public UpdateQueryBuilder With<T>(string columnName, Partial<T> partialValue) =>
partialValue.IsPresent ? With(columnName, partialValue.Value) : this;
public (string Query, DynamicParameters Parameters) Build(string suffix = "") => (_qb.Build(suffix), _params);
}