diff --git a/PluralKit.API/Startup.cs b/PluralKit.API/Startup.cs index 7ea9ad8a..ca1862c7 100644 --- a/PluralKit.API/Startup.cs +++ b/PluralKit.API/Startup.cs @@ -86,7 +86,7 @@ namespace PluralKit.API // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - Schemas.Initialize(); + Database.InitStatic(); if (env.IsDevelopment()) { diff --git a/PluralKit.Bot/Commands/ServerConfig.cs b/PluralKit.Bot/Commands/ServerConfig.cs index c0a130ec..028da9b4 100644 --- a/PluralKit.Bot/Commands/ServerConfig.cs +++ b/PluralKit.Bot/Commands/ServerConfig.cs @@ -67,7 +67,7 @@ namespace PluralKit.Bot else blacklist.UnionWith(affectedChannels.Select(c => c.Id)); await conn.ExecuteAsync("update servers set log_blacklist = @LogBlacklist where id = @Id", - new {ctx.Guild.Id, LogBlacklist = blacklist}); + new {ctx.Guild.Id, LogBlacklist = blacklist.ToArray()}); } await ctx.Reply( @@ -99,7 +99,7 @@ namespace PluralKit.Bot else blacklist.UnionWith(affectedChannels.Select(c => c.Id)); await conn.ExecuteAsync("update servers set blacklist = @Blacklist where id = @Id", - new {ctx.Guild.Id, Blacklist = blacklist}); + new {ctx.Guild.Id, Blacklist = blacklist.ToArray()}); } await ctx.Reply($"{Emojis.Success} Channels {(onBlacklist ? "added to" : "removed from")} the proxy blacklist."); diff --git a/PluralKit.Bot/Init.cs b/PluralKit.Bot/Init.cs index e815bf36..1df8b4dd 100644 --- a/PluralKit.Bot/Init.cs +++ b/PluralKit.Bot/Init.cs @@ -21,7 +21,7 @@ namespace PluralKit.Bot { // Load configuration and run global init stuff var config = InitUtils.BuildConfiguration(args).Build(); - InitUtils.Init(); + Database.InitStatic(); // Set up DI container and modules var services = BuildContainer(config); diff --git a/PluralKit.Core/Database/Database.cs b/PluralKit.Core/Database/Database.cs index 217845bd..b8328019 100644 --- a/PluralKit.Core/Database/Database.cs +++ b/PluralKit.Core/Database/Database.cs @@ -1,8 +1,13 @@ using System; +using System.Data; using System.Threading.Tasks; using App.Metrics; +using Dapper; + +using NodaTime; + using Npgsql; using Serilog; @@ -24,6 +29,29 @@ namespace PluralKit.Core _metrics = metrics; _logger = logger; } + + public static void InitStatic() + { + // Dapper by default tries to pass ulongs to Npgsql, which rejects them since PostgreSQL technically + // doesn't support unsigned types on its own. + // Instead we add a custom mapper to encode them as signed integers instead, converting them back and forth. + SqlMapper.RemoveTypeMap(typeof(ulong)); + SqlMapper.AddTypeHandler(new UlongEncodeAsLongHandler()); + SqlMapper.AddTypeHandler(new UlongArrayHandler()); + DefaultTypeMap.MatchNamesWithUnderscores = true; + + NpgsqlConnection.GlobalTypeMapper.UseNodaTime(); + // With the thing we add above, Npgsql already handles NodaTime integration + // This makes Dapper confused since it thinks it has to convert it anyway and doesn't understand the types + // So we add a custom type handler that literally just passes the type through to Npgsql + SqlMapper.AddTypeHandler(new PassthroughTypeHandler()); + SqlMapper.AddTypeHandler(new PassthroughTypeHandler()); + + // Register our custom types to Npgsql + // Without these it'll still *work* but break at the first launch + probably cause other small issues + NpgsqlConnection.GlobalTypeMapper.MapComposite("proxy_tag"); + NpgsqlConnection.GlobalTypeMapper.MapEnum("privacy_level"); + } public async Task Obtain() { @@ -48,5 +76,27 @@ namespace PluralKit.Core await using var conn = await Obtain(); return await func(conn); } + + private class PassthroughTypeHandler: SqlMapper.TypeHandler + { + public override void SetValue(IDbDataParameter parameter, T value) => parameter.Value = value; + public override T Parse(object value) => (T) value; + } + + private class UlongEncodeAsLongHandler: SqlMapper.TypeHandler + { + public override ulong Parse(object value) => + // Cast to long to unbox, then to ulong (???) + (ulong) (long) value; + + public override void SetValue(IDbDataParameter parameter, ulong value) => parameter.Value = (long) value; + } + + private class UlongArrayHandler: SqlMapper.TypeHandler + { + public override void SetValue(IDbDataParameter parameter, ulong[] value) => parameter.Value = Array.ConvertAll(value, i => (long) i); + + public override ulong[] Parse(object value) => Array.ConvertAll((long[]) value, i => (ulong) i); + } } } \ No newline at end of file diff --git a/PluralKit.Core/Database/Schemas.cs b/PluralKit.Core/Database/Schemas.cs index 49ba79b1..a0b53a02 100644 --- a/PluralKit.Core/Database/Schemas.cs +++ b/PluralKit.Core/Database/Schemas.cs @@ -25,13 +25,6 @@ namespace PluralKit.Core _logger = logger.ForContext(); } - public static void Initialize() - { - // Without these it'll still *work* but break at the first launch + probably cause other small issues - NpgsqlConnection.GlobalTypeMapper.MapComposite("proxy_tag"); - NpgsqlConnection.GlobalTypeMapper.MapEnum("privacy_level"); - } - public async Task InitializeDatabase() { // Run everything in a transaction diff --git a/PluralKit.Core/Models/GuildConfig.cs b/PluralKit.Core/Models/GuildConfig.cs index 0f4dc72c..9b112de5 100644 --- a/PluralKit.Core/Models/GuildConfig.cs +++ b/PluralKit.Core/Models/GuildConfig.cs @@ -2,7 +2,7 @@ { public class GuildConfig { - public int Id { get; } + public ulong Id { get; } public ulong? LogChannel { get; } public ulong[] LogBlacklist { get; } public ulong[] Blacklist { get; } diff --git a/PluralKit.Core/Models/ModelQueryExt.cs b/PluralKit.Core/Models/ModelQueryExt.cs index 726bdde6..2fb7e657 100644 --- a/PluralKit.Core/Models/ModelQueryExt.cs +++ b/PluralKit.Core/Models/ModelQueryExt.cs @@ -12,7 +12,7 @@ namespace PluralKit.Core conn.QueryFirstOrDefaultAsync("select * from members where id = @id", new {id}); public static Task QueryOrInsertGuildConfig(this IPKConnection conn, ulong guild) => - conn.QueryFirstAsync("insert into servers (id) values (@Guild) on conflict do nothing returning *", new {Guild = guild}); + conn.QueryFirstAsync("insert into servers (id) values (@guild) on conflict (id) do update set id = @guild returning *", new {guild}); public static Task QueryOrInsertSystemGuildConfig(this IPKConnection conn, ulong guild, int system) => conn.QueryFirstAsync( diff --git a/PluralKit.Core/Utils/DatabaseUtils.cs b/PluralKit.Core/Utils/DatabaseUtils.cs index 70849f31..38feb0c3 100644 --- a/PluralKit.Core/Utils/DatabaseUtils.cs +++ b/PluralKit.Core/Utils/DatabaseUtils.cs @@ -20,31 +20,4 @@ namespace PluralKit.Core Interlocked.Decrement(ref _connectionCount); } } - - public class PassthroughTypeHandler: SqlMapper.TypeHandler - { - public override void SetValue(IDbDataParameter parameter, T value) - { - parameter.Value = value; - } - - public override T Parse(object value) - { - return (T) value; - } - } - - public class UlongEncodeAsLongHandler: SqlMapper.TypeHandler - { - public override ulong Parse(object value) - { - // Cast to long to unbox, then to ulong (???) - return (ulong) (long) value; - } - - public override void SetValue(IDbDataParameter parameter, ulong value) - { - parameter.Value = (long) value; - } - } } \ No newline at end of file diff --git a/PluralKit.Core/Utils/InitUtils.cs b/PluralKit.Core/Utils/InitUtils.cs index 38b71197..fd016ff5 100644 --- a/PluralKit.Core/Utils/InitUtils.cs +++ b/PluralKit.Core/Utils/InitUtils.cs @@ -1,7 +1,5 @@ using System.IO; -using Dapper; - using Microsoft.Extensions.Configuration; using Newtonsoft.Json; @@ -9,8 +7,6 @@ using Newtonsoft.Json; using NodaTime; using NodaTime.Serialization.JsonNet; -using Npgsql; - namespace PluralKit.Core { public static class InitUtils { @@ -19,32 +15,6 @@ namespace PluralKit.Core { .AddJsonFile("pluralkit.conf", true) .AddEnvironmentVariables() .AddCommandLine(args); - - public static void Init() - { - InitDatabase(); - } - - private static void InitDatabase() - { - // Dapper by default tries to pass ulongs to Npgsql, which rejects them since PostgreSQL technically - // doesn't support unsigned types on its own. - // Instead we add a custom mapper to encode them as signed integers instead, converting them back and forth. - SqlMapper.RemoveTypeMap(typeof(ulong)); - SqlMapper.AddTypeHandler(new UlongEncodeAsLongHandler()); - Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true; - - // Also, use NodaTime. it's good. - NpgsqlConnection.GlobalTypeMapper.UseNodaTime(); - // With the thing we add above, Npgsql already handles NodaTime integration - // This makes Dapper confused since it thinks it has to convert it anyway and doesn't understand the types - // So we add a custom type handler that literally just passes the type through to Npgsql - SqlMapper.AddTypeHandler(new PassthroughTypeHandler()); - SqlMapper.AddTypeHandler(new PassthroughTypeHandler()); - - // Add global type mapper for ProxyTag compound type in Postgres - NpgsqlConnection.GlobalTypeMapper.MapComposite("proxy_tag"); - } public static JsonSerializerSettings BuildSerializerSettings() => new JsonSerializerSettings().BuildSerializerSettings();