Skip to content

Commit 1b5b1a6

Browse files
authored
Merge pull request #109 from mrt181/fix/ignore-struct-fields-and-empty-structs
fix: nested ignored struct fields are ignored in statements
2 parents 7516fe0 + f5a59f6 commit 1b5b1a6

File tree

6 files changed

+158
-10
lines changed

6 files changed

+158
-10
lines changed

Tests/ksqlDB.RestApi.Client.Tests/KSql/RestApi/Statements/CreateEntityTests.cs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,85 @@ public void ModelBuilder_IgnoreProperty()
759759
) WITH ( KAFKA_TOPIC='{nameof(Poco)}', VALUE_FORMAT='Json', PARTITIONS='1', REPLICAS='1' );".ReplaceLineEndings());
760760
}
761761

762+
private class PocoWithNullable : Poco
763+
{
764+
public InnerPoco InnerPoco { get; init; }
765+
}
766+
767+
private class InnerPoco
768+
{
769+
public string Value { get; init; }
770+
public InnerPoco2? InnerPoco2 { get; init; }
771+
}
772+
773+
private class InnerPoco2
774+
{
775+
public int Value { get; init; }
776+
}
777+
778+
[Test]
779+
public void ModelBuilder_IgnoreStructProperty()
780+
{
781+
//Arrange
782+
modelBuilder.Entity<PocoWithNullable>()
783+
.HasKey(x => x.Id);
784+
modelBuilder.Entity<PocoWithNullable>()
785+
.Property(c => c.InnerPoco).AsStruct();
786+
modelBuilder.Entity<PocoWithNullable>()
787+
.Property(c => c.InnerPoco.InnerPoco2).Ignore();
788+
modelBuilder.Entity<PocoWithNullable>()
789+
.Property(c => c.InnerPoco.Value);
790+
791+
var statementContext = new StatementContext
792+
{
793+
CreationType = CreationType.CreateOrReplace,
794+
KSqlEntityType = KSqlEntityType.Table,
795+
};
796+
797+
creationMetadata.KafkaTopic = nameof(Poco);
798+
799+
//Act
800+
string statement = new CreateEntity(modelBuilder).Print<PocoWithNullable>(statementContext, creationMetadata, null);
801+
802+
//Assert
803+
statement.Should().Be($@"CREATE OR REPLACE TABLE {nameof(PocoWithNullable)}s (
804+
{nameof(PocoWithNullable.InnerPoco)} STRUCT<Value VARCHAR>,
805+
{nameof(Poco.Id)} INT PRIMARY KEY,
806+
{nameof(Poco.Description)} VARCHAR
807+
) WITH ( KAFKA_TOPIC='{nameof(Poco)}', VALUE_FORMAT='Json', PARTITIONS='1', REPLICAS='1' );".ReplaceLineEndings());
808+
}
809+
810+
[Test]
811+
public void ModelBuilder_IgnoreStructWithAllIgnoredFieldsProperty()
812+
{
813+
// STRUCT<> is an invalid ksql statement
814+
815+
//Arrange
816+
modelBuilder.Entity<PocoWithNullable>()
817+
.HasKey(x => x.Id)
818+
.Property(c => c.InnerPoco).AsStruct();
819+
modelBuilder.Entity<PocoWithNullable>()
820+
.Property(c => c.InnerPoco.InnerPoco2).Ignore();
821+
modelBuilder.Entity<PocoWithNullable>()
822+
.Property(c => c.InnerPoco.Value).Ignore();
823+
824+
var statementContext = new StatementContext
825+
{
826+
CreationType = CreationType.CreateOrReplace,
827+
KSqlEntityType = KSqlEntityType.Table,
828+
};
829+
830+
creationMetadata.KafkaTopic = nameof(Poco);
831+
832+
//Act
833+
string statement = new CreateEntity(modelBuilder).Print<PocoWithNullable>(statementContext, creationMetadata, null);
834+
835+
//Assert
836+
statement.Should().Be($@"CREATE OR REPLACE TABLE {nameof(PocoWithNullable)}s (
837+
{nameof(Poco.Id)} INT PRIMARY KEY,
838+
{nameof(Poco.Description)} VARCHAR
839+
) WITH ( KAFKA_TOPIC='{nameof(Poco)}', VALUE_FORMAT='Json', PARTITIONS='1', REPLICAS='1' );".ReplaceLineEndings());
840+
}
762841
internal class IgnoreInDDL
763842
{
764843
[Key]
@@ -768,6 +847,38 @@ internal class IgnoreInDDL
768847
public string RowTime { get; set; }
769848
}
770849

850+
[Test]
851+
public void ModelBuilder_SubPropertiesOfAnIgnoredMemberAreSkipped()
852+
{
853+
// STRUCT<> is an invalid ksql statement
854+
855+
//Arrange
856+
modelBuilder.Entity<PocoWithNullable>()
857+
.HasKey(x => x.Id)
858+
.Property(c => c.InnerPoco).Ignore();
859+
modelBuilder.Entity<PocoWithNullable>()
860+
.Property(c => c.InnerPoco.InnerPoco2);
861+
modelBuilder.Entity<PocoWithNullable>()
862+
.Property(c => c.InnerPoco.Value);
863+
864+
var statementContext = new StatementContext
865+
{
866+
CreationType = CreationType.CreateOrReplace,
867+
KSqlEntityType = KSqlEntityType.Table,
868+
};
869+
870+
creationMetadata.KafkaTopic = nameof(Poco);
871+
872+
//Act
873+
string statement = new CreateEntity(modelBuilder).Print<PocoWithNullable>(statementContext, creationMetadata, null);
874+
875+
//Assert
876+
statement.Should().Be($@"CREATE OR REPLACE TABLE {nameof(PocoWithNullable)}s (
877+
{nameof(Poco.Id)} INT PRIMARY KEY,
878+
{nameof(Poco.Description)} VARCHAR
879+
) WITH ( KAFKA_TOPIC='{nameof(Poco)}', VALUE_FORMAT='Json', PARTITIONS='1', REPLICAS='1' );".ReplaceLineEndings());
880+
}
881+
771882
[Test]
772883
public void Print_IgnoreInDDLAttribute()
773884
{

ksqlDb.RestApi.Client/ChangeLog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# ksqlDB.RestApi.Client
22

3+
# 7.1.1
4+
5+
## 🐛 Bug Fixes
6+
- fixed `EntityInfo.Members` to handle nested metadata definitions and `CreateEntity.PrintProperties` to ignore empty and invalid STRUCT columns. (contributed by @mrt181)
7+
38
# 7.1.0
49

510
## 🚀 New Features

ksqlDb.RestApi.Client/KSql/RestApi/Generators/TypeGenerator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,12 @@ private static string EscapeName(string name, IdentifierEscaping escaping) =>
6363
_ => throw new ArgumentOutOfRangeException(nameof(escaping), escaping, "Non-exhaustive match")
6464
};
6565

66-
protected override bool IncludeMemberInfo(EntityMetadata? entityMetadata, MemberInfo memberInfo)
66+
protected override bool IncludeMemberInfo(Type type, EntityMetadata? entityMetadata, MemberInfo memberInfo, bool? includeReadOnly = null)
6767
{
6868
var fieldMetadata = entityMetadata?.GetFieldMetadataBy(memberInfo);
6969
if (fieldMetadata is { IgnoreInDDL: true })
7070
return false;
7171

72-
return base.IncludeMemberInfo(entityMetadata, memberInfo) && !memberInfo.GetCustomAttributes().OfType<IgnoreInDDLAttribute>().Any();
72+
return base.IncludeMemberInfo(type, entityMetadata, memberInfo) && !memberInfo.GetCustomAttributes().OfType<IgnoreInDDLAttribute>().Any();
7373
}
7474
}

ksqlDb.RestApi.Client/KSql/RestApi/Statements/CreateEntity.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,12 @@ private string TryAttachKey<T>(KSqlEntityType entityType, MemberInfo memberInfo)
108108
return $" {key}";
109109
}
110110

111-
protected override bool IncludeMemberInfo(EntityMetadata? entityMetadata, MemberInfo memberInfo)
111+
protected override bool IncludeMemberInfo(Type type, EntityMetadata? entityMetadata, MemberInfo memberInfo, bool? includeReadOnly = null)
112112
{
113113
var fieldMetadata = entityMetadata?.GetFieldMetadataBy(memberInfo);
114114
if (fieldMetadata is { IgnoreInDDL: true })
115115
return false;
116116

117-
return base.IncludeMemberInfo(entityMetadata, memberInfo) && !memberInfo.GetCustomAttributes().OfType<IgnoreInDDLAttribute>().Any();
117+
return base.IncludeMemberInfo(type, entityMetadata, memberInfo) && !memberInfo.GetCustomAttributes().OfType<IgnoreInDDLAttribute>().Any();
118118
}
119119
}

ksqlDb.RestApi.Client/KSql/RestApi/Statements/CreateInsert.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ private object GetValue<T>(InsertValues<T> insertValues, InsertProperties insert
7373
var hasValue = insertValues.PropertyValues.ContainsKey(memberInfo.Format(insertProperties.IdentifierEscaping, metadataProvider));
7474

7575
object value;
76-
76+
7777
if (hasValue)
7878
value = insertValues.PropertyValues[memberInfo.Format(insertProperties.IdentifierEscaping, metadataProvider)];
7979
else
@@ -82,12 +82,12 @@ private object GetValue<T>(InsertValues<T> insertValues, InsertProperties insert
8282
return value;
8383
}
8484

85-
protected override bool IncludeMemberInfo(EntityMetadata? entityMetadata, MemberInfo memberInfo)
85+
protected override bool IncludeMemberInfo(Type type, EntityMetadata? entityMetadata, MemberInfo memberInfo, bool? includeReadOnly = null)
8686
{
8787
var fieldMetadata = entityMetadata?.GetFieldMetadataBy(memberInfo);
8888
if (fieldMetadata is {IgnoreInDML: true})
8989
return false;
9090

91-
return base.IncludeMemberInfo(entityMetadata, memberInfo) && !memberInfo.GetCustomAttributes().OfType<IgnoreByInsertsAttribute>().Any();
91+
return base.IncludeMemberInfo(type, entityMetadata, memberInfo) && !memberInfo.GetCustomAttributes().OfType<IgnoreByInsertsAttribute>().Any();
9292
}
9393
}

ksqlDb.RestApi.Client/KSql/RestApi/Statements/EntityInfo.cs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using ksqlDB.RestApi.Client.KSql.RestApi.Statements.Annotations;
44
using ksqlDb.RestApi.Client.KSql.RestApi.Statements.Providers;
55
using ksqlDb.RestApi.Client.Metadata;
6+
using ksqlDB.RestApi.Client.Infrastructure.Extensions;
67

78
namespace ksqlDB.RestApi.Client.KSql.RestApi.Statements;
89

@@ -26,17 +27,48 @@ protected IEnumerable<MemberInfo> Members(Type type, bool? includeReadOnly = nul
2627

2728
var members = properties.Concat(fields);
2829

29-
var entityMetadata = metadataProvider.GetEntities().FirstOrDefault(c => c.Type == type);
30+
var entityMetadata = metadataProvider.GetEntities().FirstOrDefault(c => c.Type == type)
31+
?? metadataProvider.GetEntities().FirstOrDefault(c => c.FieldsMetadata.Any(fm => fm.MemberInfo.DeclaringType == type));
3032

31-
return members.Where(memberInfo => IncludeMemberInfo(entityMetadata, memberInfo));
33+
return members.Where(memberInfo => IncludeMemberInfo(type, entityMetadata, memberInfo, includeReadOnly));
3234
}
3335

34-
protected virtual bool IncludeMemberInfo(EntityMetadata? entityMetadata, MemberInfo memberInfo)
36+
protected virtual bool IncludeMemberInfo(Type type, EntityMetadata? entityMetadata, MemberInfo memberInfo, bool? includeReadOnly = null)
3537
{
3638
var fieldMetadata = entityMetadata?.GetFieldMetadataBy(memberInfo);
39+
40+
var subType = GetMemberType(memberInfo);
41+
if (!type.IsGenericType && IsStructType(subType, memberInfo))
42+
{
43+
var subMembers = Members(subType, includeReadOnly);
44+
45+
if(!subMembers.Any())
46+
return false;
47+
}
48+
3749
return fieldMetadata is not {Ignore: true} && !memberInfo.GetCustomAttributes().OfType<IgnoreAttribute>().Any();
3850
}
3951

52+
protected bool IsStructType(Type type, MemberInfo? memberInfo)
53+
{
54+
if (type.TryGetAttribute<StructAttribute>() != null)
55+
return true;
56+
57+
if (memberInfo == null)
58+
return false;
59+
60+
var entityMetadata =
61+
metadataProvider.GetEntities().FirstOrDefault(c => c.Type == type)
62+
?? metadataProvider
63+
.GetEntities()
64+
.FirstOrDefault(c => c.FieldsMetadata.Any(fm => fm.MemberInfo.DeclaringType == type));
65+
var fieldMetadata = entityMetadata?.GetFieldMetadataBy(memberInfo);
66+
return fieldMetadata is
67+
{
68+
IsStruct: true
69+
};
70+
}
71+
4072
protected static Type GetMemberType(MemberInfo memberInfo)
4173
{
4274
var type = memberInfo.MemberType switch

0 commit comments

Comments
 (0)