From 9cfdeba8be216e6772532694ba3d108add9a0439 Mon Sep 17 00:00:00 2001 From: Noikoio Date: Mon, 20 Nov 2017 19:32:34 -0800 Subject: [PATCH] Added Entry queries --- Module/ModLogs/Entry.cs | 118 ++++++++++++++++++++++++++++++++++++++-- Module/ModLogs/Sql.cs | 47 ++++++++++++++++ 2 files changed, 159 insertions(+), 6 deletions(-) create mode 100644 Module/ModLogs/Sql.cs diff --git a/Module/ModLogs/Entry.cs b/Module/ModLogs/Entry.cs index 3867b32..4e7918b 100644 --- a/Module/ModLogs/Entry.cs +++ b/Module/ModLogs/Entry.cs @@ -1,11 +1,14 @@ -using System; +using Npgsql; +using System; using System.Collections.Generic; +using System.Data.Common; using System.Text; +using System.Threading.Tasks; namespace Noikoio.RegexBot.Module.ModLogs { /// - /// Represents a log entry. + /// Represents a log entry in the database. /// class Entry { @@ -44,19 +47,122 @@ namespace Noikoio.RegexBot.Module.ModLogs /// public ulong? TargetChannel => _channelId; /// - /// Gets this log entry's 'type', or category. + /// Gets this log entry's category. /// - public string LogType => _type; + public string Category => _type; /// /// Gets the content of this log entry. /// public string Message => _message; - public Entry() + public Entry(DbDataReader r) { - throw new NotImplementedException(); + // Double-check ordinals if making changes to QueryColumns + + _logId = r.GetInt32(0); + _ts = r.GetDateTime(1).ToUniversalTime(); + unchecked + { + _guildId = (ulong)r.GetInt64(2); + _targetId = (ulong)r.GetInt64(3); + if (r.IsDBNull(4)) _invokeId = null; + else _invokeId = (ulong)r.GetInt64(4); + if (r.IsDBNull(5)) _channelId = null; + else _channelId = (ulong)r.GetInt64(5); + } + _type = r.GetString(6); + _message = r.GetString(7); } + // TODO lazy loading of channel, user, etc from caches + // TODO methods for updating this log entry(?) + // TODO figure out some helper methods to retrieve data of other entities by ID, if it becomes necessary + + #region Queries + // Double-check constructor if making changes to this constant + const string QueryColumns = "id, entry_ts, guild_id, target_id, invoke_id, target_channel_id, category, message"; + + /// + /// Attempts to look up a log entry with the given ID. + /// + /// Null if no result. + public static async Task QueryIdAsync(ulong guild, int id) + { + using (var db = await RegexBot.Config.Database.GetOpenConnectionAsync()) + { + using (var c = db.CreateCommand()) + { + c.CommandText = $"SELECT {QueryColumns} FROM {Sql.TableLog} " + + "WHERE guild_id = @Guild and id = @Id"; + c.Parameters.Add("@Guild", NpgsqlTypes.NpgsqlDbType.Bigint).Value = guild; + c.Parameters.Add("@Id", NpgsqlTypes.NpgsqlDbType.Numeric).Value = id; + c.Prepare(); + using (var r = await c.ExecuteReaderAsync()) + { + if (r.Read()) return new Entry(r); + else return null; + } + } + } + } + + public static async Task> QueryLogAsync + (ulong guild, + ulong? target = null, + ulong? invoker = null, + ulong? channel = null, + IEnumerable category = null) + { + // Enforce some limits - can't search too broadly here. Requires this at a minimum: + if (target.HasValue == false && invoker.HasValue == false) + { + throw new ArgumentNullException("Query requires at minimum searching of a target or invoker."); + } + + var result = new List(); + using (var db = await RegexBot.Config.Database.GetOpenConnectionAsync()) + { + using (var c = db.CreateCommand()) + { + c.CommandText = $"SELECT {QueryColumns} FROM {Sql.TableLog} WHERE"; + + bool and = false; + if (target.HasValue) + { + if (and) c.CommandText += " AND"; + else and = true; + c.CommandText += " target_id = @TargetId"; + c.Parameters.Add("@TargetId", NpgsqlTypes.NpgsqlDbType.Bigint).Value = target.Value; + } + if (invoker.HasValue) + { + if (and) c.CommandText += " AND"; + else and = true; + c.CommandText += " invoke_id = @InvokeId"; + c.Parameters.Add("@InvokeId", NpgsqlTypes.NpgsqlDbType.Bigint).Value = invoker.Value; + } + if (channel.HasValue) + { + if (and) c.CommandText += " AND"; + else and = true; + c.CommandText += " target_channel_id = @ChannelId"; + c.Parameters.Add("@ChannelId", NpgsqlTypes.NpgsqlDbType.Bigint).Value = channel.Value; + } + c.Prepare(); + + using (var r = await c.ExecuteReaderAsync()) + { + while (r.Read()) + { + result.Add(new Entry(r)); + } + } + } + } + + return result; + } + #endregion } } diff --git a/Module/ModLogs/Sql.cs b/Module/ModLogs/Sql.cs new file mode 100644 index 0000000..c500698 --- /dev/null +++ b/Module/ModLogs/Sql.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Noikoio.RegexBot.Module.ModLogs +{ + /// + /// Contains common constants and static methods used for accessing the log database. + /// + class Sql + { + public const string TableLog = "modlogs_entries"; + public const string TableLogIncr = TableLog + "_id"; + public const string TableMsgCache = "modlogs_msgcache"; + + static void CreateTables() + { + using (var db = RegexBot.Config.Database.GetOpenConnectionAsync().GetAwaiter().GetResult()) + { + using (var c = db.CreateCommand()) + { + c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableLog + " (" + + "id int primary key, " + + "entry_ts timestamptz not null, " + + "guild_id bigint not null, " + + "target_id bigint not null, " + + $"invoke_id bigint null references {EntityCache.Sql.TableUser}.user_id, " + + "target_channel_id bigint null, " // TODO channel cache reference? + + "category text not null, " + + "message text not null, " + + $"FOREIGN KEY (target_id, guild_id) REFERENCES {EntityCache.Sql.TableUser} (user_id, guild_id)"; + c.ExecuteNonQuery(); + } + using (var c = db.CreateCommand()) + { + c.CommandText = $"CREATE SEQUENCE IF NOT EXISTS {TableLogIncr} " + + $"START 100 MAXVALUE {int.MaxValue}"; + c.ExecuteNonQuery(); + } + } + } + + #region Log entry manipulation + // what was I doing again + #endregion + } +}