Made MessageStorage.GetMessages more flexible.

This commit is contained in:
flash 2025-04-29 12:50:32 +00:00
parent eae379e933
commit 0bc025e5f8
Signed by: flash
GPG key ID: 2C9C2C574D47FE3E
5 changed files with 167 additions and 80 deletions

View file

@ -1,6 +1,9 @@
using Microsoft.Extensions.Logging;
using MySqlConnector;
using SharpChat.Data;
using SharpChat.Messages;
using System.Data.Common;
using System.Text;
using System.Text.Json;
using ZLogger;
@ -54,20 +57,20 @@ public class MariaDBMessageStorage(MariaDBStorage storage, ILogger logger) : Mes
}
}
private static Message ReadMessage(MySqlDataReader reader) {
using Stream data = reader.GetStream("event_data");
private static Message ReadMessage(DbDataReader reader) {
using Stream data = reader.GetStream(reader.GetOrdinal("event_data"));
return new Message(
reader.GetInt64("event_id"),
reader.GetString("event_type"),
reader.IsDBNull(reader.GetOrdinal("event_sender")) ? null : reader.GetString("event_sender"),
reader.IsDBNull(reader.GetOrdinal("event_sender_name")) ? string.Empty : reader.GetString("event_sender_name"),
ColourInheritable.FromMisuzu(reader.GetInt32("event_sender_colour")),
reader.GetInt32("event_sender_rank"),
MariaDBUserPermissionsConverter.From((MariaDBUserPermissions)reader.GetInt32("event_sender_perms")),
reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? string.Empty : reader.GetString("event_sender_nick"),
DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created")),
reader.IsDBNull(reader.GetOrdinal("event_deleted")) ? null : DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_deleted")),
reader.IsDBNull(reader.GetOrdinal("event_channel")) ? null : reader.GetString("event_channel"),
reader.GetInt64(reader.GetOrdinal("event_id")),
reader.GetString(reader.GetOrdinal("event_type")),
reader.IsDBNull(reader.GetOrdinal("event_sender")) ? null : reader.GetString(reader.GetOrdinal("event_sender")),
reader.IsDBNull(reader.GetOrdinal("event_sender_name")) ? string.Empty : reader.GetString(reader.GetOrdinal("event_sender_name")),
ColourInheritable.FromMisuzu(reader.GetInt32(reader.GetOrdinal("event_sender_colour"))),
reader.GetInt32(reader.GetOrdinal("event_sender_rank")),
MariaDBUserPermissionsConverter.From((MariaDBUserPermissions)reader.GetInt32(reader.GetOrdinal("event_sender_perms"))),
reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? string.Empty : reader.GetString(reader.GetOrdinal("event_sender_nick")),
DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32(reader.GetOrdinal("event_created"))),
reader.IsDBNull(reader.GetOrdinal("event_deleted")) ? null : DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32(reader.GetOrdinal("event_deleted"))),
reader.IsDBNull(reader.GetOrdinal("event_channel")) ? null : reader.GetString(reader.GetOrdinal("event_channel")),
JsonDocument.Parse(data)
);
}
@ -100,39 +103,59 @@ public class MariaDBMessageStorage(MariaDBStorage storage, ILogger logger) : Mes
return null;
}
public async Task<IEnumerable<Message>> GetMessages(string channelName, int amount = 20, int offset = 0) {
List<Message> msgs = [];
public async Task<IEnumerable<Message>> GetMessages(
string? channelName = null,
int? take = 20,
long? beforeId = null,
bool includeDeleted = false
) {
List<MySqlParameter> parameters = [];
bool firstParam = true;
StringBuilder qb = new();
qb.Append("SELECT event_id, event_type, event_data, event_channel");
qb.Append(", event_sender, event_sender_name, event_sender_colour, event_sender_rank, event_sender_nick, event_sender_perms");
qb.Append(", UNIX_TIMESTAMP(event_created) AS event_created");
qb.Append(", UNIX_TIMESTAMP(event_deleted) AS event_deleted");
qb.Append(" FROM sqc_events");
try {
using MariaDBConnection conn = await storage.CreateConnection();
using MySqlDataReader? reader = await conn.RunQuery(
"SELECT event_id, event_type, event_data, event_channel"
+ ", event_sender, event_sender_name, event_sender_colour, event_sender_rank, event_sender_nick, event_sender_perms"
+ ", UNIX_TIMESTAMP(event_created) AS event_created"
+ ", UNIX_TIMESTAMP(event_deleted) AS event_deleted"
+ " FROM sqc_events"
+ " WHERE event_deleted IS NULL AND (event_channel = @channel OR event_channel IS NULL)"
+ " AND event_id > @offset"
+ " ORDER BY event_id DESC"
+ " LIMIT @amount",
new MySqlParameter("channel", channelName),
new MySqlParameter("amount", amount),
new MySqlParameter("offset", offset)
);
if(reader is null)
return msgs;
while(reader.Read()) {
Message evt = ReadMessage(reader);
if(evt != null)
msgs.Add(evt);
}
} catch(MySqlException ex) {
logger.ZLogError($"Error in GetMessages(): {ex}");
if(!includeDeleted) {
firstParam = false;
qb.Append(" WHERE event_deleted IS NULL");
}
msgs.Reverse();
if(!string.IsNullOrEmpty(channelName)) {
qb.AppendFormat(" {0} (event_channel = @channel OR event_channel IS NULL)", firstParam ? "WHERE" : "AND");
parameters.Add(new MySqlParameter("channel", channelName));
firstParam = false;
}
return msgs;
if(beforeId.HasValue) {
qb.AppendFormat(" {0} event_id < @before", firstParam ? "WHERE" : "AND");
parameters.Add(new MySqlParameter("before", beforeId.Value));
}
qb.Append(" ORDER BY event_id DESC");
if(take.HasValue) {
qb.Append(" LIMIT @take");
parameters.Add(new MySqlParameter("take", take.Value));
}
string query = string.Format("SELECT * FROM ({0}) AS _ ORDER BY event_id ASC", qb);
try {
MariaDBConnection conn = await storage.CreateConnection();
DbDataReader? reader = await conn.RunQuery(query, [.. parameters]);
if(reader is null) {
conn.Dispose();
return [];
}
return new DbObjectEnumerable<Message>(reader, ReadMessage, () => conn.Dispose());
} catch(MySqlException ex) {
logger.ZLogError($"Error in GetMessages({channelName}, {take}, {beforeId}, {includeDeleted}): {ex}");
return [];
}
}
}

View file

@ -1,4 +1,5 @@
using Microsoft.Extensions.Logging;
using SharpChat.Data;
using SharpChat.Messages;
using System.Data;
using System.Data.Common;
@ -79,50 +80,57 @@ public class SQLiteMessageStorage(ILogger logger, SQLiteConnection conn) : Messa
new SQLiteParameter("id", id)
);
if(reader is null)
return null;
while(reader.Read()) {
Message evt = ReadMessage(reader);
if(evt != null)
return evt;
}
return reader?.Read() == true ? ReadMessage(reader) : null;
} catch(SQLiteException ex) {
logger.ZLogError($"Error in GetMessage(): {ex}");
return null;
}
return null;
}
public async Task<IEnumerable<Message>> GetMessages(string channelName, int amount = 20, int offset = 0) {
List<Message> msgs = [];
public async Task<IEnumerable<Message>> GetMessages(
string? channelName = null,
int? take = 20,
long? beforeId = null,
bool includeDeleted = false
) {
List<SQLiteParameter> parameters = [];
bool firstParam = true;
StringBuilder qb = new();
qb.Append("SELECT msg_id, msg_type, msg_created, msg_deleted, msg_channel, msg_data");
qb.Append(", msg_sender, msg_sender_name, msg_sender_colour, msg_sender_rank, msg_sender_nick, msg_sender_perms");
qb.Append(" FROM messages");
if(!includeDeleted) {
firstParam = false;
qb.Append(" WHERE msg_deleted IS NULL");
}
if(!string.IsNullOrEmpty(channelName)) {
qb.AppendFormat(" {0} (msg_channel = @channel OR msg_channel IS NULL)", firstParam ? "WHERE" : "AND");
parameters.Add(new SQLiteParameter("channel", channelName));
firstParam = false;
}
if(beforeId.HasValue) {
qb.AppendFormat(" {0} msg_id < @before", firstParam ? "WHERE" : "AND");
parameters.Add(new SQLiteParameter("before", beforeId.Value));
}
qb.Append(" ORDER BY msg_id DESC");
if(take.HasValue) {
qb.Append(" LIMIT @take");
parameters.Add(new SQLiteParameter("take", take.Value));
}
string query = string.Format("SELECT * FROM ({0}) AS _ ORDER BY msg_id ASC", qb);
try {
using DbDataReader? reader = await conn.RunQuery(
"SELECT msg_id, msg_type, msg_created, msg_deleted, msg_channel, msg_sender, msg_sender_name, msg_sender_colour, msg_sender_rank, msg_sender_nick, msg_sender_perms, msg_data"
+ " FROM messages"
+ " WHERE msg_deleted IS NULL AND (msg_channel = @channel OR msg_channel IS NULL)"
+ " AND msg_id > @offset"
+ " ORDER BY msg_id DESC"
+ " LIMIT @amount",
new SQLiteParameter("channel", channelName),
new SQLiteParameter("amount", amount),
new SQLiteParameter("offset", offset)
);
if(reader is null)
return msgs;
while(reader.Read()) {
Message evt = ReadMessage(reader);
if(evt != null)
msgs.Add(evt);
}
DbDataReader? reader = await conn.RunQuery(query, [.. parameters]);
return reader is null ? [] : new DbObjectEnumerable<Message>(reader, ReadMessage);
} catch(SQLiteException ex) {
logger.ZLogError($"Error in GetMessages(): {ex}");
logger.ZLogError($"Error in GetMessages({channelName}, {take}, {beforeId}, {includeDeleted}): {ex}");
return [];
}
msgs.Reverse();
return msgs;
}
}

View file

@ -0,0 +1,14 @@
using System.Collections;
using System.Data.Common;
namespace SharpChat.Data;
public class DbObjectEnumerable<T>(DbDataReader reader, Func<DbDataReader, T> constructor, Action? dispose = null) : IEnumerable<T> {
public IEnumerator<T> GetEnumerator() {
return new DbObjectEnumerator<T>(reader, constructor, dispose);
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
}

View file

@ -0,0 +1,37 @@
using System.Collections;
using System.Data.Common;
namespace SharpChat.Data;
public class DbObjectEnumerator<T>(DbDataReader reader, Func<DbDataReader, T> constructor, Action? dispose) : IEnumerator<T> {
public T Current => constructor(reader);
object? IEnumerator.Current => Current;
public bool MoveNext() {
return reader.Read();
}
public void Reset() {
// no-op cus you can't reset a DbDataReader afaik
}
private bool disposed;
~DbObjectEnumerator() {
DoDispose();
}
public void Dispose() {
DoDispose();
GC.SuppressFinalize(this);
}
private void DoDispose() {
if(disposed)
return;
disposed = true;
reader.Dispose();
if(dispose is not null)
dispose();
}
}

View file

@ -15,5 +15,10 @@ public interface MessageStorage {
);
Task DeleteMessage(Message msg);
Task<Message?> GetMessage(long id);
Task<IEnumerable<Message>> GetMessages(string channelName, int amount = 20, int offset = 0);
Task<IEnumerable<Message>> GetMessages(
string? channelName = null,
int? take = 20,
long? beforeId = null,
bool includeDeleted = false
);
}