Skip to content

Commit c0da3c4

Browse files
committed
[ksqlDb.RestApi.Client]: a RowTime field or property in the registered entities will be automatically marked to be ignored in DDL statements. #93
1 parent 2feb56c commit c0da3c4

File tree

7 files changed

+111
-57
lines changed

7 files changed

+111
-57
lines changed

Tests/ksqlDB.RestApi.Client.Tests/FluentAPI/Builders/ModelBuilderTests.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,26 @@ public void Property_IgnoreInDDL()
101101
entityMetadata!.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(Payment.Description)).IgnoreInDDL.Should().BeTrue();
102102
}
103103

104+
public class RecordExt : ksqlDB.RestApi.Client.KSql.Query.Record
105+
{
106+
public string Title { get; set; }
107+
}
108+
109+
[Test]
110+
public void RowTime_PropertyConvention_IgnoreInDDL()
111+
{
112+
//Arrange
113+
114+
//Act
115+
var fieldTypeBuilder = builder.Entity<RecordExt>();
116+
117+
//Assert
118+
fieldTypeBuilder.Should().NotBeNull();
119+
var entityMetadata = ((IMetadataProvider)builder).GetEntities().FirstOrDefault(c => c.Type == typeof(RecordExt));
120+
entityMetadata.Should().NotBeNull();
121+
entityMetadata!.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(RecordExt.RowTime)).IgnoreInDDL.Should().BeTrue();
122+
}
123+
104124
[Test]
105125
public void Property_HasColumnName()
106126
{
@@ -141,6 +161,30 @@ public void MultiplePropertiesForSameType()
141161
entityMetadata.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(Payment.Amount)).Ignore.Should().BeTrue();
142162
}
143163

164+
[Test]
165+
public void MultipleMappingsForSameProperty()
166+
{
167+
//Arrange
168+
string columnName = "alter";
169+
170+
//Act
171+
var fieldTypeBuilder = builder.Entity<Payment>()
172+
.Property(b => b.Description)
173+
.HasColumnName(columnName);
174+
175+
builder.Entity<Payment>()
176+
.Property(b => b.Description)
177+
.IgnoreInDML();
178+
179+
//Assert
180+
fieldTypeBuilder.Should().NotBeNull();
181+
var entityMetadata = ((IMetadataProvider)builder).GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
182+
entityMetadata.Should().NotBeNull();
183+
var fieldMetadata = entityMetadata!.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(Payment.Description));
184+
fieldMetadata.ColumnName.Should().Be(columnName);
185+
fieldMetadata.IgnoreInDML.Should().BeTrue();
186+
}
187+
144188
[Test]
145189
public void Property_IgnoreNestedField()
146190
{

docs/modelbuilder.md

Lines changed: 8 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -369,63 +369,25 @@ INSERT INTO Actor (Id) VALUES (1);
369369
`WithHeaders` internally automatically marks the property to be ignored in DML statements.
370370

371371

372-
### IgnoreInDDL
372+
### RowTime
373373
**v6.4.0**
374374

375-
The purpose of the `IgnoreInDDL` function is to exclude specific fields from CREATE statements.
375+
The `RowTime` property in the registered `Record` entity will be automatically excluded from `CREATE` statements based on its name convention and `long` type.
376376

377377
```C#
378-
using System.ComponentModel.DataAnnotations;
378+
using ksqlDB.RestApi.Client.KSql.RestApi.Statements.Annotations;
379379

380-
public record Record
380+
public class Record
381381
{
382-
public int Id { get; set; }
383-
384-
public string Title { get; set; }
385-
382+
/// <summary>
383+
/// Row timestamp, inferred from the underlying Kafka record if not overridden.
384+
/// </summary>
386385
[PseudoColumn]
387386
public long RowTime { get; set; }
388387
}
389388
```
390389

391390
```C#
392-
using ksqlDb.RestApi.Client.FluentAPI.Builders;
393-
using ksqlDB.RestApi.Client.KSql.Query.Context;
394-
using ksqlDB.RestApi.Client.KSql.RestApi;
395-
using ksqlDB.RestApi.Client.KSql.RestApi.Http;
396-
397-
var ksqlDbUrl = "http://localhost:8088";
398-
399-
var httpClientFactory = new HttpClientFactory(new HttpClient() { BaseAddress = new Uri(ksqlDbUrl) });
400-
401-
var restApiClientOptions = new KSqlDBRestApiClientOptions
402-
{
403-
ShouldPluralizeFromItemName = false,
404-
};
405-
406391
var modelBuilder = new ModelBuilder();
407-
modelBuilder.Entity<Record>()
408-
.HasKey(c => c.Id);
409-
410-
modelBuilder.Entity<Record>()
411-
.Property(c => c.RowTime)
412-
.IgnoreInDDL();
413-
414-
var restApiClient = new KSqlDbRestApiClient(httpClientFactory, modelBuilder, restApiClientOptions);
415-
416-
EntityCreationMetadata metadata = new(kafkaTopic: nameof(Record))
417-
{
418-
Partitions = 3,
419-
Replicas = 3
420-
};
421-
422-
var httpResponseMessage = await restApiClient.CreateOrReplaceStreamAsync<Record>(metadata);
423-
```
424-
425-
Generated DDL:
426-
```SQL
427-
CREATE OR REPLACE STREAM Record (
428-
Id INT KEY,
429-
Title VARCHAR
430-
) WITH ( KAFKA_TOPIC='Record', VALUE_FORMAT='Json', PARTITIONS='3', REPLICAS='3' );
392+
modelBuilder.Entity<Record>();
431393
```

ksqlDb.RestApi.Client/ChangeLog.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22

33
# 6.4.0
44
- added the `IgnoreInDML` function to the Fluent API to exclude fields from INSERT statements #90 (proposed by @mrt181)
5-
- added the `IgnoreInDDL` function to the Fluent API to exclude fields from INSERT statements with a corresponding `IgnoreInDDLAttribute` #93
65
- added `IgnoreAttribute` to prevent properties or fields from being included in both DDL and DML statement
76
- `Headers` property was marked as obsolete in the `Record` type
87

98
## BugFix
109
- `IgnoreByInsertsAttribute` no longer excludes fields or properties from DDL statements. For this purpose, a new `IgnoreAttribute` has been introduced.
11-
- the `Record` type's fields `RowOffset` and `RowPartition` were decorated with `IgnoreAttribute` instead of `IgnoreByInsertsAttribute`, and `RowTime` was decorated with `IgnoreInDDLAttribute` instead of `IgnoreByInsertsAttribute`.
12-
10+
- the `Record` type's fields `RowOffset` and `RowPartition` are decorated with `IgnoreAttribute` instead of `IgnoreByInsertsAttribute`
11+
- the `Record` type's field `RowTime` is decorated with an internal `IgnoreInDDLAttribute` instead of `IgnoreByInsertsAttribute`
12+
1313
# 6.3.0
1414
- added `AsStruct` function to the Fluent API for marking fields as ksqldb `STRUCT` types #89 (proposed by @mrt181)
1515

ksqlDb.RestApi.Client/FluentAPI/Builders/EntityTypeBuilder.cs

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System.Linq.Expressions;
2+
using System.Reflection;
3+
using ksqlDB.RestApi.Client.KSql.Query;
24
using ksqlDb.RestApi.Client.Metadata;
35

46
namespace ksqlDb.RestApi.Client.FluentAPI.Builders
@@ -37,6 +39,8 @@ internal sealed class EntityTypeBuilder<TEntity> : EntityTypeBuilder, IEntityTyp
3739
public EntityTypeBuilder()
3840
{
3941
Metadata.Type = typeof(TEntity);
42+
43+
IgnoreRowTime();
4044
}
4145

4246
public IEntityTypeBuilder<TEntity> HasKey<TProperty>(Expression<Func<TEntity, TProperty>> getProperty)
@@ -54,12 +58,16 @@ public IFieldTypeBuilder<TProperty> Property<TProperty>(Expression<Func<TEntity,
5458
foreach (var (memberName, memberInfo) in members)
5559
{
5660
path += memberName;
57-
var fieldMetadata = new FieldMetadata()
61+
62+
if (!Metadata.FieldsMetadataDict.TryGetValue(memberInfo, out var fieldMetadata))
5863
{
59-
MemberInfo = memberInfo,
60-
Path = memberName,
61-
FullPath = path,
62-
};
64+
fieldMetadata = new FieldMetadata()
65+
{
66+
MemberInfo = memberInfo,
67+
Path = memberName,
68+
FullPath = path,
69+
};
70+
}
6371

6472
switch (typeof(TProperty))
6573
{
@@ -84,5 +92,45 @@ public IFieldTypeBuilder<TProperty> Property<TProperty>(Expression<Func<TEntity,
8492

8593
return builder;
8694
}
95+
96+
private readonly BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance;
97+
private readonly Type longType = typeof(long);
98+
private readonly string rowTime = nameof(Record.RowTime);
99+
100+
internal void IgnoreRowTime()
101+
{
102+
var props = Metadata.Type.GetProperties(bindingFlags)
103+
.Where(p => p.Name == rowTime && p.PropertyType == longType);
104+
105+
MemberInfo? propertyInfo = props.FirstOrDefault();
106+
if (propertyInfo != null)
107+
{
108+
AddFieldMetadata(propertyInfo);
109+
return;
110+
}
111+
112+
var fields = Metadata.Type.GetFields(bindingFlags)
113+
.Where(p => p.Name == rowTime && p.FieldType == longType);
114+
115+
MemberInfo? fieldInfo = fields.FirstOrDefault();
116+
117+
if (fieldInfo != null)
118+
{
119+
AddFieldMetadata(fieldInfo);
120+
}
121+
}
122+
123+
private void AddFieldMetadata(MemberInfo memberInfo)
124+
{
125+
var fieldMetadata = new FieldMetadata
126+
{
127+
MemberInfo = memberInfo,
128+
Path = memberInfo.Name,
129+
FullPath = memberInfo.Name,
130+
IgnoreInDDL = true
131+
};
132+
133+
Metadata.FieldsMetadataDict[memberInfo] = fieldMetadata;
134+
}
87135
}
88136
}

ksqlDb.RestApi.Client/FluentAPI/Builders/FieldTypeBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public interface IFieldTypeBuilder<TProperty>
1919
/// Marks the field to be excluded from data manipulation operations, preventing it from being included in DML statements such as INSERT.
2020
/// </summary>
2121
/// <returns>The field type builder for chaining additional configuration.</returns>
22-
public IFieldTypeBuilder<TProperty> IgnoreInDML();
22+
internal IFieldTypeBuilder<TProperty> IgnoreInDML();
2323

2424
/// <summary>
2525
/// Marks the field to be excluded from data definition operations, preventing it from being included in DDL statements such as CREATE STREAM or TABLE.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
2-
public sealed class IgnoreInDDLAttribute : Attribute
2+
internal sealed class IgnoreInDDLAttribute : Attribute
33
{
44
}

ksqlDb.RestApi.Client/Metadata/FieldMetadata.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public FieldMetadata(FieldMetadata fieldMetadata)
2020
internal MemberInfo MemberInfo { get; init; } = null!;
2121
public bool Ignore { get; internal set; }
2222
public bool IgnoreInDML { get; internal set; }
23-
public bool IgnoreInDDL { get; internal set; }
23+
internal bool IgnoreInDDL { get; set; }
2424
public bool HasHeaders { get; internal set; }
2525
internal bool IsStruct { get; set; }
2626
internal string Path { get; init; } = null!;

0 commit comments

Comments
 (0)