Files
Squealify/Table/BasicQueryMethodGenerator.cs
T
2025-05-13 17:05:13 +02:00

324 lines
15 KiB
C#

using Squealify.Contexts;
using Sybil;
using System;
using System.Linq;
using System.Text;
namespace Squealify.Table;
public static class BasicQueryMethodGenerator
{
public static MethodWithSqlStatement CreateInsertStatement(TableContext context)
{
var placeHolder = $"Insert{context.DboType}";
var sql = new StringBuilder();
sql.AppendLine()
.AppendLine($"\t\t\tINSERT INTO {context.TableName}")
.AppendLine($"\t\t\t\t({string.Join(", ", context.Fields.Select(f => f.Name))})")
.AppendLine("\t\t\tVALUES")
.AppendLine($"\t\t\t\t({string.Join(", ", context.Fields.Select(GetPlaceholderName))})");
var methodBuilder = SyntaxBuilder.CreateMethod(Constants.ValueTaskType, Constants.InsertMethodName)
.WithModifiers($"{Constants.Public} {Constants.Async}")
.WithParameter(context.DboType, Constants.DboArgumentName)
.WithParameter(Constants.CancellationTokenType, Constants.CancellationTokenArgument)
.WithBody(GenerateFullBody(placeHolder, context));
return new MethodWithSqlStatement(methodBuilder, placeHolder, sql.ToString());
}
public static MethodWithSqlStatement CreateUpsertStatement(TableContext context)
{
var placeHolder = $"Upsert{context.DboType}";
var sql = new StringBuilder();
sql.AppendLine()
.AppendLine($"\t\t\tINSERT INTO {context.TableName}")
.AppendLine($"\t\t\t\t({string.Join(", ", context.Fields.Select(f => f.Name))})")
.AppendLine("\t\t\tVALUES")
.AppendLine($"\t\t\t\t({string.Join(", ", context.Fields.Select(GetPlaceholderName))})")
.AppendLine($"\t\t\tON CONFLICT({context.PrimaryKey.Name}) DO UPDATE SET");
for(var i = 0; i < context.Fields.Length; i++)
{
var fieldContext = context.Fields[i];
if (fieldContext.Name == context.PrimaryKey.Name)
{
continue;
}
sql.Append($"\t\t\t\t{fieldContext.Name} = excluded.{fieldContext.Name}");
if (i < context.Fields.Length - 1)
{
sql.AppendLine(",");
}
}
sql.AppendLine(";");
var methodBuilder = SyntaxBuilder.CreateMethod(Constants.ValueTaskType, Constants.UpsertMethodName)
.WithModifiers($"{Constants.Public} {Constants.Async}")
.WithParameter(context.DboType, Constants.DboArgumentName)
.WithParameter(Constants.CancellationTokenType, Constants.CancellationTokenArgument)
.WithBody(GenerateFullBody(placeHolder, context));
return new MethodWithSqlStatement(methodBuilder, placeHolder, sql.ToString());
}
public static MethodWithSqlStatement CreateUpdateStatement(TableContext context)
{
var placeHolder = $"Update{context.DboType}";
var sql = new StringBuilder();
sql.AppendLine()
.AppendLine($"\t\t\tUPDATE {context.TableName}")
.AppendLine("\t\t\tSET");
for (var i = 0; i < context.Fields.Length; i++)
{
var fieldContext = context.Fields[i];
if (fieldContext.Name == context.PrimaryKey.Name)
{
continue;
}
sql.Append($"\t\t\t\t{fieldContext.Name} = {GetPlaceholderName(fieldContext)}");
if (i < context.Fields.Length - 1)
{
sql.AppendLine(",");
}
}
sql.AppendLine()
.AppendLine("\t\t\tWHERE")
.AppendLine($"\t\t\t\t{context.PrimaryKey.Name} = {GetPlaceholderName(context.PrimaryKey)};");
var methodBuilder = SyntaxBuilder.CreateMethod(Constants.ValueTaskType, Constants.UpdateMethodName)
.WithModifiers($"{Constants.Public} {Constants.Async}")
.WithParameter(context.DboType, Constants.DboArgumentName)
.WithParameter(Constants.CancellationTokenType, Constants.CancellationTokenArgument)
.WithBody(GenerateFullBody(placeHolder, context));
return new MethodWithSqlStatement(methodBuilder, placeHolder, sql.ToString());
}
public static MethodWithSqlStatement CreateDeleteStatement(TableContext context)
{
var placeHolder = $"Delete{context.DboType}";
var sql = new StringBuilder();
sql.AppendLine()
.AppendLine($"\t\t\tDELETE FROM {context.TableName}")
.AppendLine($"\t\t\tWHERE {context.PrimaryKey.Name} = {GetPlaceholderName(context.PrimaryKey)};");
var bodyBuilder = new StringBuilder(@$"
using var command = this.{Constants.DbConnectionPropertyName}.CreateCommand();
command.CommandText = @""{placeHolder}"";
");
if (!DataTypeMappings.DataTypeToString.TryGetValue(context.PrimaryKey.Type, out var convertedType))
{
throw new InvalidOperationException($"Unable to find conversion for {context.PrimaryKey.Type}");
}
(var convertToName, _) = SyntaxParsers.GetConversionNames(context.PrimaryKey.PropertyType, convertedType);
var valueProvider = context.PrimaryKey.RequiresConversion
? $"this.{convertToName}({Constants.PrimaryKeyArgumentName})"
: $"{Constants.PrimaryKeyArgumentName}";
bodyBuilder.AppendLine($"command.Parameters.Add(this.{Constants.CreateParameterMethod}(command, \"{GetPlaceholderName(context.PrimaryKey)}\", {valueProvider}));");
bodyBuilder.AppendLine("await command.ExecuteNonQueryAsync(cancellationToken);");
var methodBuilder = SyntaxBuilder.CreateMethod(Constants.ValueTaskType, Constants.DeleteMethodName)
.WithModifiers($"{Constants.Public} {Constants.Async}")
.WithParameter(context.PrimaryKey.PropertyType, Constants.PrimaryKeyArgumentName)
.WithParameter(Constants.CancellationTokenType, Constants.CancellationTokenArgument)
.WithBody(bodyBuilder.ToString());
return new MethodWithSqlStatement(methodBuilder, placeHolder, sql.ToString());
}
public static MethodWithSqlStatement CreateFindStatement(TableContext context)
{
var placeHolder = $"Find{context.DboType}";
var sql = new StringBuilder();
sql.AppendLine()
.AppendLine($"\t\t\tSELECT * FROM {context.TableName}")
.AppendLine($"\t\t\tWHERE {context.PrimaryKey.Name} = {GetPlaceholderName(context.PrimaryKey)};");
var bodyBuilder = new StringBuilder(@$"
using var command = this.{Constants.DbConnectionPropertyName}.CreateCommand();
command.CommandText = @""{placeHolder}"";
");
if (!DataTypeMappings.DataTypeToString.TryGetValue(context.PrimaryKey.Type, out var convertedType))
{
throw new InvalidOperationException($"Unable to find conversion for {context.PrimaryKey.Type}");
}
(var convertToName, _) = SyntaxParsers.GetConversionNames(context.PrimaryKey.PropertyType, convertedType);
var valueProvider = context.PrimaryKey.RequiresConversion
? $"this.{convertToName}({Constants.PrimaryKeyArgumentName})"
: $"{Constants.PrimaryKeyArgumentName}";
bodyBuilder.AppendLine($"command.Parameters.Add(this.{Constants.CreateParameterMethod}(command, \"{GetPlaceholderName(context.PrimaryKey)}\", {valueProvider}));");
bodyBuilder.AppendLine($"using var reader = await command.ExecuteReaderAsync({Constants.CancellationTokenArgument});");
bodyBuilder.AppendLine($"if (!await reader.ReadAsync({Constants.CancellationTokenArgument})) return default;");
bodyBuilder.AppendLine($"return new {context.DboType}");
bodyBuilder.AppendLine("{");
foreach((var index, var fieldContext) in context.Fields.Select((f, i) => (i, f)))
{
if (!DataTypeMappings.DataTypeToString.TryGetValue(fieldContext.Type, out var convertedFieldType))
{
continue;
}
(_, var convertFromName) = SyntaxParsers.GetConversionNames(fieldContext.PropertyType, convertedFieldType);
var fieldValueProvider = fieldContext.RequiresConversion
? $"this.{convertFromName}({GetReaderMethod("reader", fieldContext)}({index}))"
: $"{GetReaderMethod("reader", fieldContext)}({index})";
var nullCheckGuardedProvider = fieldContext.IsNullable
? $"await reader.IsDBNullAsync({index}, {Constants.CancellationTokenArgument}) ? default : {fieldValueProvider}"
: fieldValueProvider;
bodyBuilder.Append($"{fieldContext.PropertyName} = {nullCheckGuardedProvider}");
if (index < context.Fields.Length - 1)
{
bodyBuilder.AppendLine(",");
}
}
bodyBuilder
.AppendLine()
.AppendLine("};");
var methodBuilder = SyntaxBuilder.CreateMethod($"{Constants.ValueTaskType}<{context.DboType}?>", Constants.FindMethodName)
.WithModifiers($"{Constants.Public} {Constants.Async}")
.WithParameter(context.PrimaryKey.PropertyType, Constants.PrimaryKeyArgumentName)
.WithParameter(Constants.CancellationTokenType, Constants.CancellationTokenArgument)
.WithBody(bodyBuilder.ToString());
return new MethodWithSqlStatement(methodBuilder, placeHolder, sql.ToString());
}
public static MethodWithSqlStatement CreateFindAllStatement(TableContext context)
{
var placeHolder = $"FindAll{context.DboType}";
var sql = new StringBuilder();
sql.AppendLine()
.AppendLine($"\t\t\tSELECT * FROM {context.TableName};");
var bodyBuilder = new StringBuilder(@$"
using var command = this.{Constants.DbConnectionPropertyName}.CreateCommand();
command.CommandText = @""{placeHolder}"";
");
if (!DataTypeMappings.DataTypeToString.TryGetValue(context.PrimaryKey.Type, out var convertedType))
{
throw new InvalidOperationException($"Unable to find conversion for {context.PrimaryKey.Type}");
}
bodyBuilder.AppendLine($"using var reader = await command.ExecuteReaderAsync({Constants.CancellationTokenArgument});");
bodyBuilder.AppendLine($"while (await reader.ReadAsync({Constants.CancellationTokenArgument}))");
bodyBuilder.AppendLine("{");
bodyBuilder.AppendLine($"yield return new {context.DboType}");
bodyBuilder.AppendLine("{");
foreach ((var index, var fieldContext) in context.Fields.Select((f, i) => (i, f)))
{
if (!DataTypeMappings.DataTypeToString.TryGetValue(fieldContext.Type, out var convertedFieldType))
{
continue;
}
(_, var convertFromName) = SyntaxParsers.GetConversionNames(fieldContext.PropertyType, convertedFieldType);
var fieldValueProvider = fieldContext.RequiresConversion
? $"this.{convertFromName}({GetReaderMethod("reader", fieldContext)}({index}))"
: $"{GetReaderMethod("reader", fieldContext)}({index})";
var nullCheckGuardedProvider = fieldContext.IsNullable
? $"await reader.IsDBNullAsync({index}, {Constants.CancellationTokenArgument}) ? default : {fieldValueProvider}"
: fieldValueProvider;
bodyBuilder.Append($"{fieldContext.PropertyName} = {nullCheckGuardedProvider}");
if (index < context.Fields.Length - 1)
{
bodyBuilder.AppendLine(",");
}
}
bodyBuilder
.AppendLine()
.AppendLine("};")
.AppendLine("}");
var methodBuilder = SyntaxBuilder.CreateMethod($"{Constants.IAsyncEnumerableType}<{context.DboType}>", Constants.FindAllMethodName)
.WithModifiers($"{Constants.Public} {Constants.Async}")
.WithParameter(context.PrimaryKey.PropertyType, Constants.PrimaryKeyArgumentName)
.WithParameter($"[{Constants.EnumeratorCancellationAttribute}] {Constants.CancellationTokenType}", Constants.CancellationTokenArgument)
.WithBody(bodyBuilder.ToString());
return new MethodWithSqlStatement(methodBuilder, placeHolder, sql.ToString());
}
private static string GenerateFullBody(string placeHolder, TableContext context)
{
var bodyBuilder = new StringBuilder(@$"
using var command = this.{Constants.DbConnectionPropertyName}.CreateCommand();
command.CommandText = @""{placeHolder}"";
");
foreach (var fieldContext in context.Fields)
{
var propertyType = fieldContext.PropertyType;
var dataType = fieldContext.Type;
if (!DataTypeMappings.DataTypeToString.TryGetValue(dataType, out var convertedType))
{
continue;
}
(var convertToName, _) = SyntaxParsers.GetConversionNames(propertyType, convertedType);
var valueProvider = fieldContext.RequiresConversion
? $"this.{convertToName}({Constants.DboArgumentName}.{fieldContext.PropertyName})"
: $"{Constants.DboArgumentName}.{fieldContext.PropertyName}";
bodyBuilder.AppendLine($"command.Parameters.Add(this.{Constants.CreateParameterMethod}(command, \"{GetPlaceholderName(fieldContext)}\", {valueProvider}));");
}
bodyBuilder.AppendLine("await command.ExecuteNonQueryAsync(cancellationToken);");
return bodyBuilder.ToString();
}
private static string GetPlaceholderName(FieldContext context) => $"@{context.Name}";
private static string GetReaderMethod(string readerVariableName, FieldContext context)
{
var readMethod = context.Type switch
{
DataTypes.SMALLINT => "GetInt16",
DataTypes.INTEGER => "GetInt32",
DataTypes.BIGINT => "GetInt64",
DataTypes.REAL => "GetFloat",
DataTypes.DOUBLE_PRECISION => "GetDouble",
DataTypes.DECIMAL => "GetDecimal",
DataTypes.CHAR => "GetChar",
DataTypes.VARCHAR => "GetString",
DataTypes.TEXT => "GetString",
DataTypes.DATE => "GetDateTime",
DataTypes.TIME => "GetDateTime",
DataTypes.TIMESTAMP => "GetDateTime",
DataTypes.BOOLEAN => "GetBoolean",
DataTypes.BLOB => "GetBytes",
_ => throw new NotSupportedException($"Data type {context.Type} is not supported.")
};
var readerMethod = context.IsEnum
? $"({context.PropertyType}){readerVariableName}.{readMethod}"
: $"{readerVariableName}.{readMethod}";
if(context.PropertyType.ToLower().TrimEnd('?') is "datetimeoffset" or "system.datetimeoffset")
{
return context.IsNullable
? $"(DateTimeOffset?){readerMethod}"
: $"(DateTimeOffset){readerMethod}";
}
return readerMethod;
}
}