Skip to content

Commit d265805

Browse files
authored
Merge pull request #92 from tomasfabian/90-specify-ignorebyinserts-using-the-model-builder
90 specify ignorebyinserts using the model builder
2 parents 821edbe + 6256854 commit d265805

File tree

18 files changed

+215
-42
lines changed

18 files changed

+215
-42
lines changed

Tests/ksqlDB.RestApi.Client.IntegrationTests/KSql/Linq/ModelBuilderTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
using ksqlDB.RestApi.Client.KSql.RestApi.Http;
1010
using ksqlDB.RestApi.Client.KSql.RestApi.Statements;
1111
using ksqlDB.RestApi.Client.KSql.RestApi.Statements.Properties;
12-
using Microsoft.VisualStudio.TestTools.UnitTesting;
1312
using NUnit.Framework;
1413

1514
namespace ksqlDb.RestApi.Client.IntegrationTests.KSql.Linq
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
using ksqlDB.RestApi.Client.KSql.RestApi.Statements.Annotations;
1+
using ksqlDB.RestApi.Client.KSql.RestApi.Statements.Annotations;
22

33
namespace ksqlDb.RestApi.Client.IntegrationTests.Models;
44

55
public record Record
66
{
7-
[IgnoreByInserts]
7+
[ksqlDB.RestApi.Client.KSql.RestApi.Statements.Annotations.Ignore]
8+
[PseudoColumn]
89
public long RowTime { get; set; }
9-
}
10+
}

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public void HasKey_Field()
5050
}
5151

5252
[Test]
53-
public void Property_IgnoreField()
53+
public void Property_Ignore()
5454
{
5555
//Arrange
5656

@@ -67,6 +67,23 @@ public void Property_IgnoreField()
6767
entityMetadata!.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(Payment.Description)).Ignore.Should().BeTrue();
6868
}
6969

70+
[Test]
71+
public void Property_IgnoreInDML()
72+
{
73+
//Arrange
74+
75+
//Act
76+
var fieldTypeBuilder = builder.Entity<Payment>()
77+
.Property(b => b.Description)
78+
.IgnoreInDML();
79+
80+
//Assert
81+
fieldTypeBuilder.Should().NotBeNull();
82+
var entityMetadata = ((IMetadataProvider)builder).GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
83+
entityMetadata.Should().NotBeNull();
84+
entityMetadata!.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(Payment.Description)).IgnoreInDML.Should().BeTrue();
85+
}
86+
7087
[Test]
7188
public void Property_HasColumnName()
7289
{
@@ -233,6 +250,7 @@ public void Headers()
233250
var metadata = entityMetadata!.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(Payment.Header));
234251

235252
metadata.HasHeaders.Should().BeTrue();
253+
metadata.IgnoreInDML.Should().BeTrue();
236254
}
237255

238256
private record KeyValuePair

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,38 @@ public void Generate_UseModelBuilder_Ignore()
149149
statement.Should().Be($"INSERT INTO {insertProperties.EntityName} (Title, Id) VALUES ('Title', 1);");
150150
}
151151

152+
private class Actor
153+
{
154+
[Key]
155+
public int Id { get; set; }
156+
public string Name { get; set; } = null!;
157+
}
158+
159+
public static IEnumerable<(IdentifierEscaping, string)> GenerateIgnoreInDMLTestCases()
160+
{
161+
yield return (Never, "INSERT INTO Actors (Id) VALUES (1);");
162+
yield return (Keywords, "INSERT INTO Actors (Id) VALUES (1);");
163+
yield return (Always, "INSERT INTO `Actors` (`Id`) VALUES (1);");
164+
}
165+
166+
[TestCaseSource(nameof(GenerateIgnoreInDMLTestCases))]
167+
public void Generate_UseModelBuilder_IgnoreInDML((IdentifierEscaping escaping, string expected) testCase)
168+
{
169+
//Arrange
170+
modelBuilder.Entity<Actor>()
171+
.Property(c => c.Name)
172+
.IgnoreInDML();
173+
174+
var (escaping, expected) = testCase;
175+
var actor = new Actor { Id = 1, Name = "E.T." };
176+
177+
//Act
178+
var statement = new CreateInsert(modelBuilder).Generate(actor, new InsertProperties { IdentifierEscaping = escaping });
179+
180+
//Assert
181+
statement.Should().Be(expected);
182+
}
183+
152184
[Test]
153185
public void Generate_ShouldNotPluralizeEntityName()
154186
{

Tests/ksqlDB.RestApi.Client.Tests/Models/Movies/Movie.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ namespace ksqlDb.RestApi.Client.Tests.Models.Movies;
44

55
public class Movie
66
{
7-
[IgnoreByInserts]
7+
[ksqlDB.RestApi.Client.KSql.RestApi.Statements.Annotations.Ignore]
88
public long RowTime { get; set; }
99
public string Title { get; set; } = null!;
1010
[Key]
1111
public int Id { get; set; }
1212
public int Release_Year { get; set; }
1313

14-
[IgnoreByInserts]
14+
[ksqlDB.RestApi.Client.KSql.RestApi.Statements.Annotations.Ignore]
1515
public int IgnoreMe { get; set; }
1616

1717
public IEnumerable<int> ReadOnly { get; } = new[] { 1, 2 };

docs/ksqldbrestapiclient.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1311,3 +1311,22 @@ var restApiClientOptions = new KSqlDBRestApiClientOptions
13111311
};
13121312
servicesCollection.AddSingleton(restApiClientOptions);
13131313
```
1314+
1315+
1316+
## IgnoreAttribute
1317+
**v6.4.0**
1318+
1319+
Properties and fields decorated with the `IgnoreAttribute` are excluded from both DDL and DML statements.
1320+
1321+
```C#
1322+
public class Movie
1323+
{
1324+
[ksqlDB.RestApi.Client.KSql.RestApi.Statements.Annotations.Key]
1325+
public int Id { get; set; }
1326+
public string Title { get; set; }
1327+
public int Release_Year { get; set; }
1328+
1329+
[ksqlDB.RestApi.Client.KSql.RestApi.Statements.Annotations.Ignore]
1330+
public int IgnoredProperty { get; set; }
1331+
}
1332+
```

docs/modelbuilder.md

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,18 @@ public static async Task InitModelAndCreateTableAsync(CancellationToken cancella
3434
BaseAddress = new Uri("http://localhost:8088")
3535
};
3636
var httpClientFactory = new HttpClientFactory(httpClient);
37-
var restApiProvider = new KSqlDbRestApiClient(httpClientFactory, builder);
37+
var restApiClient = new KSqlDbRestApiClient(httpClientFactory, builder);
3838

3939
var entityCreationMetadata = new EntityCreationMetadata(kafkaTopic: nameof(Payment), partitions: 3);
4040

41-
var responseMessage = await restApiProvider.CreateTableAsync<Payment>(entityCreationMetadata, true, cancellationToken);
41+
var responseMessage = await restApiClient.CreateTableAsync<Payment>(entityCreationMetadata, true, cancellationToken);
4242
var content = await responseMessage.Content.ReadAsStringAsync(cancellationToken);
4343

4444
entityCreationMetadata = new EntityCreationMetadata(kafkaTopic: nameof(Account), partitions: 1)
4545
{
4646
Replicas = 3
4747
};
48-
responseMessage = await restApiProvider.CreateTableAsync<Account>(entityCreationMetadata, true, cancellationToken);
48+
responseMessage = await restApiClient.CreateTableAsync<Account>(entityCreationMetadata, true, cancellationToken);
4949
}
5050
```
5151

@@ -189,7 +189,7 @@ var entityCreationMetadata = new EntityCreationMetadata(kafkaTopic: nameof(PocoW
189189
{
190190
Replicas = 3
191191
};
192-
var responseMessage = await restApiProvider.CreateStreamAsync<PocoWithHeader>(entityCreationMetadata, true);
192+
var responseMessage = await restApiClient.CreateStreamAsync<PocoWithHeader>(entityCreationMetadata, true);
193193

194194
private record PocoWithHeader
195195
{
@@ -317,3 +317,53 @@ CREATE TABLE IF NOT EXISTS Records (
317317
Headers ARRAY<STRUCT<Key VARCHAR, Value BYTES>>
318318
) WITH ( KAFKA_TOPIC='my_topic', VALUE_FORMAT='Json', PARTITIONS='3' );
319319
```
320+
321+
### IgnoreInDML
322+
**v6.4.0**
323+
324+
The purpose of the `IgnoreInDML` function is to exclude specific fields from INSERT statements.
325+
326+
```C#
327+
using System.ComponentModel.DataAnnotations;
328+
329+
public class Actor
330+
{
331+
[Key]
332+
public int Id { get; set; }
333+
public string Name { get; set; } = null!;
334+
}
335+
```
336+
337+
```C#
338+
using ksqlDb.RestApi.Client.FluentAPI.Builders;
339+
using ksqlDB.RestApi.Client.KSql.Query.Context;
340+
using ksqlDB.RestApi.Client.KSql.RestApi;
341+
using ksqlDB.RestApi.Client.KSql.RestApi.Http;
342+
343+
var ksqlDbUrl = "http://localhost:8088";
344+
345+
var httpClientFactory = new HttpClientFactory(new HttpClient() { BaseAddress = new Uri(ksqlDbUrl) });
346+
347+
var restApiClientOptions = new KSqlDBRestApiClientOptions
348+
{
349+
ShouldPluralizeFromItemName = false,
350+
};
351+
352+
var restApiClient = new KSqlDbRestApiClient(httpClientFactory, restApiClientOptions);
353+
354+
var modelBuilder = new ModelBuilder();
355+
modelBuilder.Entity<Actor>()
356+
.Property(c => c.Name)
357+
.IgnoreInDML();
358+
359+
var actor = new Actor { Id = 1, Name = "Matthew David McConaughey" };
360+
361+
var ksql = restApiClient.ToInsertStatement(actor);
362+
```
363+
364+
Generated KSQL:
365+
```SQL
366+
INSERT INTO Actor (Id) VALUES (1);
367+
```
368+
369+
`WithHeaders` internally automatically marks the property to be ignored in DML statements.

ksqlDb.RestApi.Client/ChangeLog.md

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

3+
# 6.4.0
4+
- added the `IgnoreInDML` function to the Fluent API to exclude fields from INSERT statements #90 (proposed by @mrt181)
5+
- added `IgnoreAttribute` to prevent properties or fields from being included in both DDL and DML statement
6+
- `Headers` property was marked as obsolete in the `Record` type
7+
8+
## BugFix
9+
- `IgnoreByInsertsAttribute` no longer excludes fields or properties from DDL statements. For this purpose, a new `IgnoreAttribute` has been introduced.
10+
311
# 6.3.0
412
- added `AsStruct` function to the Fluent API for marking fields as ksqldb `STRUCT` types #89 (proposed by @mrt181)
513

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Linq.Expressions;
12
using ksqlDb.RestApi.Client.Metadata;
23

34
namespace ksqlDb.RestApi.Client.FluentAPI.Builders
@@ -9,11 +10,17 @@ namespace ksqlDb.RestApi.Client.FluentAPI.Builders
910
public interface IFieldTypeBuilder<TProperty>
1011
{
1112
/// <summary>
12-
/// Marks the field as ignored, excluding it from the entity's schema.
13+
/// Marks the field as ignored, excluding it from the entity's schema, preventing it from being included in both DDL and DML statements.
1314
/// </summary>
1415
/// <returns>The field type builder for chaining additional configuration.</returns>
1516
public IFieldTypeBuilder<TProperty> Ignore();
1617

18+
/// <summary>
19+
/// Marks the field to be excluded from data manipulation operations, preventing it from being included in DML statements such as INSERT.
20+
/// </summary>
21+
/// <returns>The field type builder for chaining additional configuration.</returns>
22+
public IFieldTypeBuilder<TProperty> IgnoreInDML();
23+
1724
/// <summary>
1825
/// Marks the field as HEADERS.
1926
/// </summary>
@@ -55,9 +62,16 @@ public IFieldTypeBuilder<TProperty> Ignore()
5562
return this;
5663
}
5764

65+
public IFieldTypeBuilder<TProperty> IgnoreInDML()
66+
{
67+
fieldMetadata.IgnoreInDML = true;
68+
return this;
69+
}
70+
5871
public IFieldTypeBuilder<TProperty> WithHeaders()
5972
{
6073
fieldMetadata.HasHeaders = true;
74+
IgnoreInDML();
6175
return this;
6276
}
6377
}

ksqlDb.RestApi.Client/KSql/Query/Record.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,32 @@ public class Record
1010
/// <summary>
1111
/// Columns that are populated by the Kafka record's header.
1212
/// </summary>
13+
[Ignore]
1314
[IgnoreByInserts]
1415
[PseudoColumn]
16+
[Obsolete("This property will be removed in the future. Headers need to be defined per use case and should have the type ARRAY<STRUCT<key STRING, value BYTES>>.")]
1517
public string? Headers { get; set; }
1618

1719
/// <summary>
1820
/// The offset of the source record.
1921
/// </summary>
22+
[Ignore]
2023
[IgnoreByInserts]
2124
[PseudoColumn]
2225
public long? RowOffset { get; set; }
2326

2427
/// <summary>
2528
/// The partition of the source record.
2629
/// </summary>
30+
[Ignore]
2731
[IgnoreByInserts]
2832
[PseudoColumn]
2933
public short? RowPartition { get; set; }
3034

3135
/// <summary>
3236
/// Row timestamp, inferred from the underlying Kafka record if not overridden.
3337
/// </summary>
38+
[Ignore]
3439
[IgnoreByInserts]
3540
[PseudoColumn]
3641
public long RowTime { get; set; }

ksqlDb.RestApi.Client/KSql/RestApi/Extensions/MemberInfoExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ internal static class MemberInfoExtensions
1212
/// </summary>
1313
/// <param name="memberInfo"></param>
1414
/// <param name="escaping"></param>
15-
/// <param name="modelBuilder"></param>
15+
/// <param name="metadataProvider"></param>
1616
/// <returns>the <c>memberInfo.Name</c> modified based on the provided <c>format</c></returns>
17-
public static string Format(this MemberInfo memberInfo, IdentifierEscaping escaping, ModelBuilder modelBuilder) => IdentifierUtil.Format(memberInfo, escaping, modelBuilder);
17+
public static string Format(this MemberInfo memberInfo, IdentifierEscaping escaping, IMetadataProvider metadataProvider) => IdentifierUtil.Format(memberInfo, escaping, metadataProvider);
1818
}
1919
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace ksqlDB.RestApi.Client.KSql.RestApi.Statements.Annotations
2+
{
3+
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
4+
public sealed class IgnoreAttribute : Attribute
5+
{
6+
}
7+
}

0 commit comments

Comments
 (0)