Files
hermes-server/Store/Internal/GroupSaveSqlGenerator.cs

204 lines
8.4 KiB
C#

using System.Reflection;
using System.Text;
using System.Text.Json;
using HermesSocketLibrary.db;
namespace HermesSocketServer.Store.Internal
{
public class GroupSaveSqlGenerator<T>
{
private readonly IDictionary<string, PropertyInfo?> _columnPropertyRelations;
private readonly IDictionary<string, string> _columnTypes;
private readonly Serilog.ILogger _logger;
public GroupSaveSqlGenerator(IDictionary<string, string> columnsToProperties, Serilog.ILogger logger)
: this(columnsToProperties, new Dictionary<string, string>(), logger)
{
}
public GroupSaveSqlGenerator(IDictionary<string, string> columnsToProperties, IDictionary<string, string> 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<int> DoPreparedStatement<V>(Database database, string sql, IEnumerable<V> 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<int> DoPreparedStatementRaw<V>(Database database, string sql, IEnumerable<V> 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<string> columns, IDictionary<string, string> 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<string> keyColumns, IEnumerable<string> updateColumns, IDictionary<string, string> 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<string> keyColumns, IDictionary<string, string> 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();
}
}
}