Skip to content

Commit

Permalink
[ksqlDB.RestApi.Client]: added HasColumnName to Fluent API for custom…
Browse files Browse the repository at this point in the history
…izing column names
  • Loading branch information
tomasfabian committed May 31, 2024
1 parent 910042c commit 37845cc
Show file tree
Hide file tree
Showing 16 changed files with 190 additions and 43 deletions.
13 changes: 13 additions & 0 deletions ksqlDb.RestApi.Client/FluentAPI/Builders/FieldTypeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,24 @@ public interface IFieldTypeBuilder<TProperty>
/// </summary>
/// <returns>The field type builder for chaining additional configuration.</returns>
public IFieldTypeBuilder<TProperty> WithHeaders();

/// <summary>
/// Configures the column name that the property will be mapped to in the record schema.
/// </summary>
/// <param name="columnName">The name of the column in the record schema.</param>
/// <returns>The same <see cref="IFieldTypeBuilder{TProperty}"/> instance so that multiple calls can be chained.</returns>
IFieldTypeBuilder<TProperty> HasColumnName(string columnName);
}

internal class FieldTypeBuilder<TProperty>(FieldMetadata fieldMetadata)
: IFieldTypeBuilder<TProperty>
{
public IFieldTypeBuilder<TProperty> HasColumnName(string columnName)
{
fieldMetadata.ColumnName = columnName;
return this;
}

public IFieldTypeBuilder<TProperty> Ignore()
{
fieldMetadata.Ignore = true;
Expand Down
27 changes: 25 additions & 2 deletions ksqlDb.RestApi.Client/Infrastructure/Extensions/TypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using System.Collections;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;
using ksqlDb.RestApi.Client.FluentAPI.Builders;
using ksqlDB.RestApi.Client.KSql.Linq;
using ksqlDB.RestApi.Client.KSql.Query;
using ksqlDB.RestApi.Client.KSql.RestApi.Statements.Annotations;
using ksqlDb.RestApi.Client.Metadata;

namespace ksqlDB.RestApi.Client.Infrastructure.Extensions;

Expand Down Expand Up @@ -101,9 +104,29 @@ internal static string ExtractTypeName(this Type type)

return attribute;
}

internal static string GetMemberName(this MemberInfo memberInfo)

internal static string GetMemberName(this MemberExpression memberExpression, ModelBuilder? modelBuilder)
{
var entityMetadata = modelBuilder?.GetEntities().FirstOrDefault(c => c.Type == memberExpression.Expression?.Type);

return memberExpression.Member.GetMemberName(entityMetadata);
}

internal static string GetMemberName(this MemberInfo memberInfo, ModelBuilder? modelBuilder)
{
var entityMetadata = modelBuilder?.GetEntities().FirstOrDefault(c => c.Type == memberInfo.DeclaringType);

return memberInfo.GetMemberName(entityMetadata);
}

internal static string GetMemberName(this MemberInfo memberInfo, EntityMetadata? entityMetadata)
{
var fieldMetadata =
entityMetadata?.FieldsMetadata.FirstOrDefault(c => c.MemberInfo.Name == memberInfo.Name);

if (fieldMetadata != null && !string.IsNullOrEmpty(fieldMetadata.ColumnName))
return fieldMetadata.ColumnName;

var jsonPropertyNameAttribute = memberInfo.GetCustomAttribute<JsonPropertyNameAttribute>();

var memberName = jsonPropertyNameAttribute?.Name ?? memberInfo.Name;
Expand Down
2 changes: 2 additions & 0 deletions ksqlDb.RestApi.Client/KSql/Query/Context/KSqlDBContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using ksqlDB.RestApi.Client.KSql.RestApi.Statements.Inserts;
using ksqlDB.RestApi.Client.KSql.RestApi.Statements.Properties;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;

namespace ksqlDB.RestApi.Client.KSql.Query.Context;
Expand Down Expand Up @@ -72,6 +73,7 @@ protected override void OnConfigureServices(IServiceCollection serviceCollection
{
base.OnConfigureServices(serviceCollection, contextOptions);

serviceCollection.TryAddSingleton(modelBuilder);
serviceCollection.RegisterEndpointDependencies(contextOptions);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ protected override Expression VisitConstant(ConstantExpression constantExpressio
}
else if (value != null && type != null && (type.IsClass || type.IsStruct() || type.IsDictionary()))
{
var ksqlValue = new CreateKSqlValue(QueryMetadata.ModelBuilder).ExtractValue(value, null, null, type, str => IdentifierUtil.Format(str, QueryMetadata.IdentifierEscaping));
var ksqlValue = new CreateKSqlValue(QueryMetadata.ModelBuilder).ExtractValue(value, null, null, type, memberInfo => IdentifierUtil.Format(memberInfo, QueryMetadata.IdentifierEscaping, QueryMetadata.ModelBuilder));

StringBuilder.Append(ksqlValue);
}
Expand Down
4 changes: 2 additions & 2 deletions ksqlDb.RestApi.Client/KSql/Query/Visitors/KSqlJoinsVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ protected override Expression VisitMember(MemberExpression memberExpression)

if (memberExpression.Expression?.NodeType == ExpressionType.Parameter)
{
var memberName = IdentifierUtil.Format(memberExpression.Member, QueryMetadata.IdentifierEscaping);
var memberName = IdentifierUtil.Format(memberExpression, QueryMetadata.IdentifierEscaping, QueryMetadata.ModelBuilder);

Append(memberName);

Expand All @@ -200,7 +200,7 @@ protected override Expression VisitMember(MemberExpression memberExpression)

if (QueryMetadata.Joins != null && memberExpression.Expression?.NodeType == ExpressionType.MemberAccess)
{
Append(memberExpression.Member.Format(QueryMetadata.IdentifierEscaping));
Append(memberExpression.Member.Format(QueryMetadata.IdentifierEscaping, QueryMetadata.ModelBuilder));
}
else
base.VisitMember(memberExpression);
Expand Down
31 changes: 16 additions & 15 deletions ksqlDb.RestApi.Client/KSql/Query/Visitors/KSqlVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ protected override Expression VisitMemberInit(MemberInitExpression node)
else
Append(ColumnsSeparator);

var memberName = memberBinding.Member.Format(QueryMetadata.IdentifierEscaping);
var memberName = memberBinding.Member.Format(QueryMetadata.IdentifierEscaping, QueryMetadata.ModelBuilder);

Append($"{memberName} := ");

Expand Down Expand Up @@ -346,7 +346,7 @@ private protected void PrintColumnWithAlias(MemberInfo memberInfo, Expression ex
{
Visit(expression);
Append(" AS ");
Append(memberInfo.Format(QueryMetadata.IdentifierEscaping));
Append(memberInfo.Format(QueryMetadata.IdentifierEscaping, QueryMetadata.ModelBuilder));
}

protected virtual void ProcessVisitNewMember(MemberInfo memberInfo, Expression expression)
Expand All @@ -355,14 +355,14 @@ protected virtual void ProcessVisitNewMember(MemberInfo memberInfo, Expression e
{
Visit(expression);

Append(" " + memberInfo.Format(QueryMetadata.IdentifierEscaping));
Append(" " + memberInfo.Format(QueryMetadata.IdentifierEscaping, QueryMetadata.ModelBuilder));
return;
}

if (expression is MemberExpression { Expression: MemberExpression { Expression: not null } me1 } &&
me1.Expression.Type.IsKsqlGrouping())
{
Append(memberInfo.Format(QueryMetadata.IdentifierEscaping));
Append(memberInfo.Format(QueryMetadata.IdentifierEscaping, QueryMetadata.ModelBuilder));
return;
}

Expand All @@ -379,14 +379,14 @@ protected virtual void ProcessVisitNewMember(MemberInfo memberInfo, Expression e
break;
case MemberExpression me2 when me2.Member.GetCustomAttribute<JsonPropertyNameAttribute>() != null ||
me2.Member.GetCustomAttribute<PseudoColumnAttribute>() != null:
QueryMetadata.EntityMetadata.Add(me2.Member);
Append(me2.Member.Format(QueryMetadata.IdentifierEscaping));
QueryMetadata.EntityMetadata.Add(me2);
Append(me2.Member.Format(QueryMetadata.IdentifierEscaping, QueryMetadata.ModelBuilder));
break;
case MemberExpression { Expression.NodeType: ExpressionType.Constant }:
Visit(expression);
break;
default:
Append(memberInfo.Format(QueryMetadata.IdentifierEscaping));
Append(memberInfo.Format(QueryMetadata.IdentifierEscaping, QueryMetadata.ModelBuilder));
break;
}
}
Expand All @@ -401,7 +401,7 @@ protected override Expression VisitMember(MemberExpression memberExpression)
{
var foundFromItem = QueryMetadata.TrySetAlias(memberExpression, (_, alias) => string.IsNullOrEmpty(alias));

var memberName = memberExpression.Member.Format(QueryMetadata.IdentifierEscaping);
var memberName = memberExpression.Member.Format(QueryMetadata.IdentifierEscaping, QueryMetadata.ModelBuilder);

var alias = IdentifierUtil.Format(((ParameterExpression)memberExpression.Expression).Name!,
QueryMetadata.IdentifierEscaping);
Expand All @@ -417,15 +417,15 @@ protected override Expression VisitMember(MemberExpression memberExpression)

if (fromItem != null && memberExpression.Expression?.NodeType == ExpressionType.MemberAccess)
{
string alias = ((MemberExpression)memberExpression.Expression).Member.Format(QueryMetadata.IdentifierEscaping);
string alias = ((MemberExpression)memberExpression.Expression).Member.Format(QueryMetadata.IdentifierEscaping, QueryMetadata.ModelBuilder);

fromItem.Alias = alias;

Append(alias);

Append(".");

var memberName = memberExpression.Member.Format(QueryMetadata.IdentifierEscaping);
var memberName = memberExpression.Member.Format(QueryMetadata.IdentifierEscaping, QueryMetadata.ModelBuilder);
Append(memberName);
return memberExpression;
}
Expand All @@ -439,8 +439,8 @@ protected override Expression VisitMember(MemberExpression memberExpression)

return memberExpression;
}

var memberName2 = memberExpression.Member.GetMemberName();
var memberName2 = memberExpression.GetMemberName(QueryMetadata.ModelBuilder);

switch (memberExpression.Expression.NodeType)
{
Expand Down Expand Up @@ -510,8 +510,9 @@ private void AppendVisitMemberParameter(MemberExpression memberExpression)

if (type != fromItem?.Type)
{
var memberInfo = QueryMetadata.EntityMetadata.TryGetMemberInfo(memberExpression.Member.Name) ?? memberExpression.Member;
Append(memberInfo.Format(QueryMetadata.IdentifierEscaping));
memberExpression = QueryMetadata.EntityMetadata.TryGetMemberExpression(memberExpression.Member.Name) ?? memberExpression;

Append(IdentifierUtil.Format(memberExpression, QueryMetadata.IdentifierEscaping, QueryMetadata.ModelBuilder));
}
}

Expand All @@ -538,7 +539,7 @@ protected void Destructure(MemberExpression memberExpression)
if (fromItem == null)
Append("->");

var memberName = memberExpression.Member.Format(QueryMetadata.IdentifierEscaping);
var memberName = memberExpression.Member.Format(QueryMetadata.IdentifierEscaping, QueryMetadata.ModelBuilder);

Append(memberName);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Reflection;
using ksqlDb.RestApi.Client.FluentAPI.Builders;
using ksqlDB.RestApi.Client.KSql.RestApi.Enums;
using ksqlDb.RestApi.Client.KSql.RestApi.Parsers;

Expand All @@ -11,7 +12,8 @@ internal static class MemberInfoExtensions
/// </summary>
/// <param name="memberInfo"></param>
/// <param name="escaping"></param>
/// <param name="modelBuilder"></param>
/// <returns>the <c>memberInfo.Name</c> modified based on the provided <c>format</c></returns>
public static string Format(this MemberInfo memberInfo, IdentifierEscaping escaping) => IdentifierUtil.Format(memberInfo, escaping);
public static string Format(this MemberInfo memberInfo, IdentifierEscaping escaping, ModelBuilder modelBuilder) => IdentifierUtil.Format(memberInfo, escaping, modelBuilder);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text;
using ksqlDb.RestApi.Client.FluentAPI.Builders;
using ksqlDB.RestApi.Client.Infrastructure.Extensions;
using ksqlDB.RestApi.Client.KSql.RestApi.Enums;
using ksqlDb.RestApi.Client.KSql.RestApi.Parsers;
using ksqlDB.RestApi.Client.KSql.RestApi.Statements;
Expand Down Expand Up @@ -35,7 +36,8 @@ private void PrintProperties<T>(StringBuilder stringBuilder, IdentifierEscaping

var ksqlType = typeTranslator.Translate(type, escaping);

var columnDefinition = $"{EscapeName(memberInfo.Name, escaping)} {ksqlType}{typeTranslator.ExploreAttributes(typeof(T), memberInfo, type)}";
var memberName = memberInfo.GetMemberName(modelBuilder);
var columnDefinition = $"{EscapeName(memberName, escaping)} {ksqlType}{typeTranslator.ExploreAttributes(typeof(T), memberInfo, type)}";
ksqlProperties.Add(columnDefinition);
}

Expand Down
28 changes: 28 additions & 0 deletions ksqlDb.RestApi.Client/KSql/RestApi/Json/JsonTypeInfoResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;

namespace ksqlDb.RestApi.Client.KSql.RestApi.Json
{
internal class JsonTypeInfoResolver(IJsonTypeInfoResolver typeInfoResolver) : IJsonTypeInfoResolver
{
private readonly IJsonTypeInfoResolver typeInfoResolver = typeInfoResolver ?? throw new ArgumentNullException(nameof(typeInfoResolver));

public IList<Action<JsonTypeInfo>> Modifiers => modifiers ??= new List<Action<JsonTypeInfo>>();
private IList<Action<JsonTypeInfo>>? modifiers;

public virtual JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options)
{
var typeInfo = typeInfoResolver.GetTypeInfo(type, options);

if (modifiers != null)
{
foreach (Action<JsonTypeInfo> modifier in modifiers)
{
modifier(typeInfo);

Check warning on line 21 in ksqlDb.RestApi.Client/KSql/RestApi/Json/JsonTypeInfoResolver.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'obj' in 'void Action<JsonTypeInfo>.Invoke(JsonTypeInfo obj)'.

Check warning on line 21 in ksqlDb.RestApi.Client/KSql/RestApi/Json/JsonTypeInfoResolver.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'obj' in 'void Action<JsonTypeInfo>.Invoke(JsonTypeInfo obj)'.

Check warning on line 21 in ksqlDb.RestApi.Client/KSql/RestApi/Json/JsonTypeInfoResolver.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'obj' in 'void Action<JsonTypeInfo>.Invoke(JsonTypeInfo obj)'.

Check warning on line 21 in ksqlDb.RestApi.Client/KSql/RestApi/Json/JsonTypeInfoResolver.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'obj' in 'void Action<JsonTypeInfo>.Invoke(JsonTypeInfo obj)'.
}
}

return typeInfo;
}
}
}
53 changes: 52 additions & 1 deletion ksqlDb.RestApi.Client/KSql/RestApi/KSqlDbQueryStreamProvider.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
#if !NETSTANDARD
using System.Net;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using ksqlDb.RestApi.Client.FluentAPI.Builders;
using ksqlDb.RestApi.Client.KSql.Query.Context.Options;
using ksqlDB.RestApi.Client.KSql.RestApi.Exceptions;
using ksqlDB.RestApi.Client.KSql.RestApi.Responses;
using Microsoft.Extensions.Logging;
using IHttpClientFactory = ksqlDB.RestApi.Client.KSql.RestApi.Http.IHttpClientFactory;
using JsonTypeInfoResolver = ksqlDb.RestApi.Client.KSql.RestApi.Json.JsonTypeInfoResolver;

#nullable disable
namespace ksqlDB.RestApi.Client.KSql.RestApi
{
internal class KSqlDbQueryStreamProvider : KSqlDbProvider
{
public KSqlDbQueryStreamProvider(IHttpClientFactory httpClientFactory, KSqlDbProviderOptions options, ILogger logger = null)
private readonly ModelBuilder modelBuilder;

public KSqlDbQueryStreamProvider(IHttpClientFactory httpClientFactory, ModelBuilder modelBuilder, KSqlDbProviderOptions options, ILogger logger = null)
: base(httpClientFactory, options, logger)
{
this.modelBuilder = modelBuilder ?? throw new ArgumentNullException(nameof(modelBuilder));
#if NETCOREAPP3_1
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
#endif
Expand Down Expand Up @@ -48,6 +54,51 @@ protected override RowValue<T> OnLineRead<T>(string rawJson)
return default;
}

protected override JsonSerializerOptions OnCreateJsonSerializerOptions()
{
var jsonSerializerOptions = base.OnCreateJsonSerializerOptions();

if (jsonSerializerOptions.TypeInfoResolver == null)
{
var defaultJsonTypeInfoResolver = new DefaultJsonTypeInfoResolver();
var resolver = new JsonTypeInfoResolver(defaultJsonTypeInfoResolver)
{
Modifiers = { JsonPropertyNameModifier }
};
jsonSerializerOptions.TypeInfoResolver = resolver;
}
else if(jsonSerializerOptions.TypeInfoResolver is not JsonTypeInfoResolver)
{
var resolver = new JsonTypeInfoResolver(jsonSerializerOptions.TypeInfoResolver)
{
Modifiers = { JsonPropertyNameModifier }
};

jsonSerializerOptions.TypeInfoResolver = resolver;
}

return jsonSerializerOptions;
}

internal void JsonPropertyNameModifier(JsonTypeInfo jsonTypeInfo)
{
JsonPropertyNameModifier(jsonTypeInfo, modelBuilder);
}

internal static void JsonPropertyNameModifier(JsonTypeInfo jsonTypeInfo, ModelBuilder modelBuilder)
{
var entityMetadata = modelBuilder.GetEntities().FirstOrDefault(c => c.Type == jsonTypeInfo.Type);

foreach (var typeInfoProperty in jsonTypeInfo.Properties)
{
var fieldMetadata =
entityMetadata?.FieldsMetadata.FirstOrDefault(c => c.MemberInfo.Name == typeInfoProperty.Name);

if (fieldMetadata != null && !string.IsNullOrEmpty(fieldMetadata.ColumnName))
typeInfoProperty.Name = fieldMetadata.ColumnName;
}
}

private static void OnError<T>(string rawJson)
{
var errorResponse = JsonSerializer.Deserialize<ErrorResponse>(rawJson);
Expand Down
Loading

0 comments on commit 37845cc

Please sign in to comment.