Finished implementing DatabaseConfig

No longer using separate schemas for each guild. Values for multiple
guilds will be stored within the same table.
This commit is contained in:
Noikoio 2017-11-05 14:58:50 -08:00
parent 97b3bddf20
commit 2958d14b08
2 changed files with 70 additions and 53 deletions

View file

@ -47,7 +47,7 @@ namespace Noikoio.RegexBot.ConfigItem
_enabled = true; _enabled = true;
} }
private async Task<NpgsqlConnection> P_OpenConnectionAsync(ulong? guildId = null) public async Task<NpgsqlConnection> GetOpenConnectionAsync()
{ {
if (!Enabled) return null; if (!Enabled) return null;
@ -58,30 +58,10 @@ namespace Noikoio.RegexBot.ConfigItem
Password = _pass, Password = _pass,
Database = _dbname Database = _dbname
}; };
if (guildId.HasValue) cs.SearchPath = "g_" + guildId.Value.ToString();
var db = new NpgsqlConnection(cs.ToString()); var db = new NpgsqlConnection(cs.ToString());
await db.OpenAsync(); await db.OpenAsync();
return db; return db;
} }
public Task<NpgsqlConnection> OpenConnectionAsync(ulong guildId) => P_OpenConnectionAsync(guildId);
public async Task CreateGuildSchemaAsync(ulong gid)
{
if (!Enabled) return;
const string cs = "CREATE SCHEMA IF NOT EXISTS {0}";
string sn = "g_" + gid.ToString();
using (var db = await P_OpenConnectionAsync())
{
using (var c = db.CreateCommand())
{
c.CommandText = string.Format(cs, sn);
await c.ExecuteNonQueryAsync();
}
}
}
} }
} }

View file

@ -1,6 +1,7 @@
using Discord.WebSocket; using Discord.WebSocket;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Noikoio.RegexBot.ConfigItem; using Noikoio.RegexBot.ConfigItem;
using NpgsqlTypes;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -21,10 +22,17 @@ namespace Noikoio.RegexBot.Feature.DBCache
{ {
_db = RegexBot.Config.Database; _db = RegexBot.Config.Database;
client.GuildAvailable += Client_GuildAvailable; if (_db.Enabled)
client.GuildUpdated += Client_GuildUpdated; {
client.GuildMemberUpdated += Client_GuildMemberUpdated; client.GuildAvailable += Client_GuildAvailable;
// it may not be necessary to handle JoinedGuild, as GuildAvailable still provides info client.GuildUpdated += Client_GuildUpdated;
client.GuildMemberUpdated += Client_GuildMemberUpdated;
// it may not be necessary to handle JoinedGuild, as GuildAvailable provides this info
}
else
{
Log("No database storage available.").Wait();
}
} }
public override Task<object> ProcessConfiguration(JToken configSection) => Task.FromResult<object>(null); public override Task<object> ProcessConfiguration(JToken configSection) => Task.FromResult<object>(null);
@ -33,7 +41,6 @@ namespace Noikoio.RegexBot.Feature.DBCache
// Guild _and_ guild member information has become available // Guild _and_ guild member information has become available
private async Task Client_GuildAvailable(SocketGuild arg) private async Task Client_GuildAvailable(SocketGuild arg)
{ {
if (!_db.Enabled) return;
await CreateCacheTables(arg.Id); await CreateCacheTables(arg.Id);
await Task.Run(() => UpdateGuild(arg)); await Task.Run(() => UpdateGuild(arg));
@ -43,77 +50,107 @@ namespace Noikoio.RegexBot.Feature.DBCache
// Guild information has changed // Guild information has changed
private async Task Client_GuildUpdated(SocketGuild arg1, SocketGuild arg2) private async Task Client_GuildUpdated(SocketGuild arg1, SocketGuild arg2)
{ {
if (!_db.Enabled) return;
await Task.Run(() => UpdateGuild(arg2)); await Task.Run(() => UpdateGuild(arg2));
} }
// Guild member information has changed // Guild member information has changed
private async Task Client_GuildMemberUpdated(SocketGuildUser arg1, SocketGuildUser arg2) private async Task Client_GuildMemberUpdated(SocketGuildUser arg1, SocketGuildUser arg2)
{ {
if (!_db.Enabled) return;
await Task.Run(() => UpdateGuildMember(arg2)); await Task.Run(() => UpdateGuildMember(arg2));
} }
#endregion #endregion
#region Table setup #region Table setup
const string TableGuild = "cache_guild"; public const string TableGuild = "cache_guild";
const string TableUser = "cache_users"; const string TableUser = "cache_users";
private async Task CreateCacheTables(ulong gid) private async Task CreateCacheTables(ulong gid)
{ {
/* Note: using (var db = await _db.GetOpenConnectionAsync())
* We save information per guild in their own schemas named "g_NUM", where NUM is the Guild ID.
*
* The creation of these schemas is handled within here, but we're possibly facing a short delay
* in the event that other events that we're listening for come in without a schema having been
* created yet in which to put them in.
* Got to figure that out.
*/
await _db.CreateGuildSchemaAsync(gid);
using (var db = await _db.OpenConnectionAsync(gid))
{ {
Task<int> c1, c2;
// Uh... didn't think this through. For now this is a table that'll only ever have one column.
// Got to rethink this in particular.
using (var c = db.CreateCommand()) using (var c = db.CreateCommand())
{ {
c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableGuild + "(" c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableGuild + "("
+ "snowflake bigint primary key, " + "guild_id bigint primary key, "
+ "current_name text not null, " + "current_name text not null, "
+ "display_name text null" + "display_name text null"
+ ")"; + ")";
c1 = c.ExecuteNonQueryAsync(); // TODO determine if other columns necessary?
await c.ExecuteNonQueryAsync();
} }
using (var c = db.CreateCommand()) using (var c = db.CreateCommand())
{ {
c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableUser + "(" c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableUser + "("
+ "snowflake bigint primary key, " + "user_id bigint, "
+ "guild_id bigint references " + TableGuild + " (guild_id), "
+ "cache_date timestamptz not null, " + "cache_date timestamptz not null, "
+ "username text not null, " + "username text not null, "
+ "discriminator text not null, " + "discriminator text not null, "
+ "nickname text null, " + "nickname text null, "
+ "avatar_url text null" + "avatar_url text null"
+ ")"; + ")";
c2 = c.ExecuteNonQueryAsync(); await c.ExecuteNonQueryAsync();
}
using (var c = db.CreateCommand())
{
c.CommandText = "CREATE UNIQUE INDEX IF NOT EXISTS "
+ $"{TableUser}_idx on {TableUser} (user_id, guild_id)";
await c.ExecuteNonQueryAsync();
} }
await c1;
await c2;
} }
} }
#endregion #endregion
private async Task UpdateGuild(SocketGuild g) private async Task UpdateGuild(SocketGuild g)
{ {
throw new NotImplementedException(); using (var db = await _db.GetOpenConnectionAsync())
{
using (var c = db.CreateCommand())
{
c.CommandText = "INSERT INTO " + TableGuild + " (guild_id, current_name) "
+ "(@GuildId, @CurrentName) "
+ "ON CONFLICT (guild_id) DO UPDATE SET "
+ "current_name = EXCLUDED.current_name";
c.Parameters.Add("@GuildID", NpgsqlDbType.Bigint).Value = g.Id;
c.Parameters.Add("@CurrentName", NpgsqlDbType.Text).Value = g.Name;
c.Prepare();
await c.ExecuteNonQueryAsync();
}
}
} }
private async Task UpdateGuildMember(ulong gid, IEnumerable<SocketGuildUser> users) private async Task UpdateGuildMember(ulong gid, IEnumerable<SocketGuildUser> users)
{ {
throw new NotImplementedException(); using (var db = await _db.GetOpenConnectionAsync())
{
using (var c = db.CreateCommand())
{
c.CommandText = "INSERT INTO " + TableUser + " VALUES "
+ "(@Uid, @Gid, @Date, @Uname, @Disc, @Nname, @Url) "
+ "ON CONFLICT (user_id, guild_id) DO UPDATE SET "
+ "cache_date = EXCLUDED.cache_date, username = EXCLUDED.username, "
+ "discriminator = EXCLUDED.discriminator, " // I've seen someone's discriminator change this one time...
+ "nickname = EXCLUDED.nickname, avatar_url = EXCLUDED.avatar_url";
c.Prepare();
var now = DateTime.Now;
List<Task> inserts = new List<Task>();
foreach (var item in users)
{
c.Parameters.Clear();
c.Parameters.Add("@Uid", NpgsqlDbType.Bigint).Value = item.Id;
c.Parameters.Add("@Gid", NpgsqlDbType.Bigint).Value = item.Guild.Id;
c.Parameters.Add("@Date", NpgsqlDbType.TimestampTZ).Value = now;
c.Parameters.Add("@Uname", NpgsqlDbType.Text).Value = item.Username;
c.Parameters.Add("@Disc", NpgsqlDbType.Text).Value = item.Discriminator;
c.Parameters.Add("@Nname", NpgsqlDbType.Text).Value = item.Nickname;
c.Parameters.Add("@Url", NpgsqlDbType.Text).Value = item.GetAvatarUrl();
await c.ExecuteNonQueryAsync();
}
}
}
} }
private Task UpdateGuildMember(SocketGuildUser user) private Task UpdateGuildMember(SocketGuildUser user)