using System.Reflection; using System.Text; using System.Text.Json; using HermesSocketLibrary.db; namespace HermesSocketServer.Store.Internal { public class GroupSaveSqlGenerator { private readonly IDictionary _columnPropertyRelations; private readonly IDictionary _columnTypes; private readonly Serilog.ILogger _logger; public GroupSaveSqlGenerator(IDictionary columnsToProperties, Serilog.ILogger logger) : this(columnsToProperties, new Dictionary(), logger) { } public GroupSaveSqlGenerator(IDictionary columnsToProperties, IDictionary columnTypes, Serilog.ILogger logger) { var type = typeof(T); _columnPropertyRelations = columnsToProperties.ToDictionary(p => p.Key, p => type.GetProperty(p.Value)); _columnTypes = columnTypes; _logger = logger; var nullProperties = _columnPropertyRelations.Where(p => p.Value == null) .Select(p => columnsToProperties[p.Key]); if (nullProperties.Any()) throw new ArgumentException("Some properties do not exist on the values given: " + string.Join(", ", nullProperties)); } public async Task DoPreparedStatement(Database database, string sql, IEnumerable values, string[] columns) { try { return await database.Execute(sql, (c) => { var valueCounter = 0; foreach (var value in values) { foreach (var column in columns) { var propValue = _columnPropertyRelations[column]!.GetValue(value); if (_columnTypes.Any() && _columnTypes.TryGetValue(column, out var type)) { if (type == "jsonb") propValue = JsonSerializer.Serialize(propValue); } c.Parameters.AddWithValue(column.ToLower() + valueCounter, propValue ?? DBNull.Value); } valueCounter++; } }); } catch (Exception ex) { _logger.Error(ex, "Failed to execute a prepared statement: " + sql); return -1; } } public async Task DoPreparedStatementRaw(Database database, string sql, IEnumerable values, string[] columns) { try { return await database.Execute(sql, (c) => { var valueCounter = 0; foreach (var value in values) { foreach (var column in columns) { object? propValue = value; c.Parameters.AddWithValue(column.ToLower() + valueCounter, propValue ?? DBNull.Value); } valueCounter++; } }); } catch (Exception ex) { _logger.Error(ex, "Failed to execute a prepared statement: " + sql); return -1; } } public string GeneratePreparedInsertSql(string table, int rows, IEnumerable columns, IDictionary typeMapping) { if (string.IsNullOrWhiteSpace(table)) throw new ArgumentException("Value is either null or whitespace-filled.", nameof(table)); if (columns == null) throw new ArgumentNullException(nameof(columns)); if (!columns.Any()) throw new ArgumentException("Empty list given.", nameof(columns)); var ctp = columns.ToDictionary(c => c, c => _columnPropertyRelations[c]); var sb = new StringBuilder(); sb.Append($"INSERT INTO \"{table}\" (\"{string.Join("\", \"", columns)}\") VALUES "); for (var row = 0; row < rows; row++) { sb.Append("("); foreach (var column in columns) { sb.Append('@') .Append(column.ToLower()) .Append(row); if (typeMapping.TryGetValue(column, out var type)) sb.Append("::\"") .Append(type) .Append("\""); sb.Append(", "); } sb.Remove(sb.Length - 2, 2) .Append("),"); } sb.Remove(sb.Length - 1, 1) .Append(';'); return sb.ToString(); } public string GeneratePreparedUpdateSql(string table, int rows, IEnumerable keyColumns, IEnumerable updateColumns, IDictionary typeMapping) { if (string.IsNullOrWhiteSpace(table)) throw new ArgumentException("Value is either null or whitespace-filled.", nameof(table)); if (keyColumns == null) throw new ArgumentNullException(nameof(keyColumns)); if (!keyColumns.Any()) throw new ArgumentException("Empty list given.", nameof(keyColumns)); if (updateColumns == null) throw new ArgumentNullException(nameof(updateColumns)); if (!updateColumns.Any()) throw new ArgumentException("Empty list given.", nameof(updateColumns)); var columns = keyColumns.Union(updateColumns); var ctp = columns.ToDictionary(c => c, c => _columnPropertyRelations[c]); var sb = new StringBuilder(); sb.Append($"UPDATE \"{table}\" as t SET {string.Join(", ", updateColumns.Select(c => "\"" + c + "\" = c.\"" + c + "\""))} FROM (VALUES "); for (var row = 0; row < rows; row++) { sb.Append("("); foreach (var column in columns) { sb.Append('@') .Append(column) .Append(row); if (typeMapping.TryGetValue(column, out var type)) sb.Append("::\"") .Append(type) .Append("\""); sb.Append(", "); } sb.Remove(sb.Length - 2, 2) .Append("),"); } sb.Remove(sb.Length - 1, 1) .Append($") AS c(\"{string.Join("\", \"", columns)}\") WHERE ") .Append(string.Join(" AND ", keyColumns.Select(c => "t.\"" + c + "\" = c.\"" + c + "\""))) .Append(";"); return sb.ToString(); } public string GeneratePreparedDeleteSql(string table, int rows, IEnumerable keyColumns, IDictionary typeMapping) { if (string.IsNullOrWhiteSpace(table)) throw new ArgumentException("Value is either null or whitespace-filled.", nameof(table)); if (keyColumns == null) throw new ArgumentNullException(nameof(keyColumns)); if (!keyColumns.Any()) throw new ArgumentException("Empty list given.", nameof(keyColumns)); var ctp = keyColumns.ToDictionary(c => c, c => _columnPropertyRelations[c]); var sb = new StringBuilder(); sb.Append($"DELETE FROM \"{table}\" WHERE (\"{string.Join("\", \"", keyColumns)}\") IN ("); for (var row = 0; row < rows; row++) { sb.Append("("); foreach (var column in keyColumns) { sb.Append('@') .Append(column) .Append(row); if (typeMapping.TryGetValue(column, out var type)) sb.Append("::\"") .Append(type) .Append("\""); sb.Append(", "); } sb.Remove(sb.Length - 2, 2) .Append("),"); } sb.Remove(sb.Length - 1, 1) .Append(");"); return sb.ToString(); } } }