Skip to content

Commit 61d6f60

Browse files
authored
Merge pull request #83 from tomasfabian/82-enhancing-fluent-api-implementing-the-hascolumnname-method
82 enhancing fluent API implementing the HasColumnName method
2 parents 910042c + da83047 commit 61d6f60

File tree

50 files changed

+1057
-149
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1057
-149
lines changed

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -369,10 +369,14 @@ modelBuilder.Entity<Payment>()
369369

370370
modelBuilder.Entity<Payment>()
371371
.Property(b => b.Description)
372-
.Ignore();
372+
.HasColumnName("Desc");
373373

374374
modelBuilder.Entity<Account>()
375375
.HasKey(c => c.Id);
376+
377+
modelBuilder.Entity<Account>()
378+
.Property(b => b.Secret)
379+
.Ignore();
376380
```
377381

378382
C# entity definitions:
@@ -389,6 +393,7 @@ record Account
389393
{
390394
public string Id { get; set; } = null!;
391395
public decimal Balance { get; set; }
396+
public string Secret { get; set; }
392397
}
393398
```
394399

@@ -408,15 +413,16 @@ responseMessage = await restApiProvider.CreateTableAsync<Account>(entityCreation
408413
Generated KSQL:
409414

410415
```SQL
411-
CREATE TYPE Payment AS STRUCT<Id VARCHAR, Amount DECIMAL(10,2)>;
416+
CREATE TYPE Payment AS STRUCT<Id VARCHAR, Amount DECIMAL(10,2), Desc VARCHAR>;
412417

413418
CREATE TABLE IF NOT EXISTS Accounts (
414419
Id VARCHAR PRIMARY KEY,
415420
Balance DECIMAL(14,14)
416421
) WITH ( KAFKA_TOPIC='Account', VALUE_FORMAT='Json', PARTITIONS='3', REPLICAS='3' );
417422
```
418423

419-
The `Description` field in the `Payment` type is ignored during code generation, and the `Id` field in the `Account` table is marked as the **primary key**.
424+
The Description property within the `Payment` type has been customized to override the resulting column name as "Desc".
425+
Additionally, the `Id` property within the `Account` table has been designated as the **primary key**, while the `Secret` property is disregarded during code generation.
420426

421427
### Aggregation functions
422428
List of supported ksqldb [aggregation functions](https://github.com/tomasfabian/ksqlDB.RestApi.Client-DotNet/blob/main/docs/aggregations.md):

Samples/ksqlDB.RestApi.Client.Sample/ModelBuilders/PaymentModelBuilder.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ public async Task InitModelAndCreateStreamAsync(CancellationToken cancellationTo
2525
.Property(b => b.Amount)
2626
.Decimal(precision: 10, scale: 2);
2727

28+
modelBuilder.Entity<Payment>()
29+
.Property(b => b.Description)
30+
.HasColumnName("desc");
31+
2832
string header = "abc";
2933
modelBuilder.Entity<PocoWithHeader>()
3034
.Property(c => c.Header)
@@ -59,7 +63,7 @@ private IKSqlDbRestApiClient ConfigureRestApiClientWithServicesCollection(Servic
5963
{
6064
c.UseKSqlDb(ksqlDbUrl);
6165

62-
c.ReplaceHttpClient<ksqlDB.RestApi.Client.KSql.RestApi.Http.IHttpClientFactory, ksqlDB.RestApi.Client.KSql.RestApi.Http.HttpClientFactory>(_ => { })
66+
c.ReplaceHttpClient<KSql.RestApi.Http.IHttpClientFactory, KSql.RestApi.Http.HttpClientFactory>(_ => { })
6367
.AddHttpMessageHandler(_ => new Program.DebugHandler());
6468
})
6569
.AddSingleton(builder);

Samples/ksqlDB.RestApi.Client.Sample/ksqlDB.RestApi.Client.Samples.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
</PropertyGroup>
1010

1111
<ItemGroup>
12-
<PackageReference Include="ksqlDb.RestApi.Client" Version="6.0.0" />
13-
<!-- <ProjectReference Include="..\..\ksqlDb.RestApi.Client\ksqlDb.RestApi.Client.csproj" /> -->
12+
<!-- <PackageReference Include="ksqlDb.RestApi.Client" Version="6.1.0" /> -->
13+
<ProjectReference Include="..\..\ksqlDb.RestApi.Client\ksqlDb.RestApi.Client.csproj" />
1414
<!-- <PackageReference Include="ksqlDb.RestApi.Client.ProtoBuf" Version="4.0.0" /> -->
1515
<ProjectReference Include="..\..\ksqlDb.RestApi.Client.ProtoBuf\ksqlDb.RestApi.Client.ProtoBuf.csproj" />
1616

Tests/ksqlDB.RestApi.Client.IntegrationTests/Infrastructure/IntegrationTests.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using ksqlDb.RestApi.Client.FluentAPI.Builders;
12
using ksqlDb.RestApi.Client.IntegrationTests.KSql.RestApi;
23
using ksqlDB.RestApi.Client.KSql.Query.Context;
34
using ksqlDB.RestApi.Client.KSql.Query.Options;
@@ -32,15 +33,16 @@ public override void TestInitialize()
3233
Context = CreateKSqlDbContext(EndpointType.QueryStream);
3334
}
3435

35-
protected KSqlDBContext CreateKSqlDbContext(EndpointType endpointType)
36+
protected KSqlDBContext CreateKSqlDbContext(EndpointType endpointType, ModelBuilder? modelBuilder = null)
3637
{
3738
ContextOptions = new KSqlDBContextOptions(KSqlDbRestApiProvider.KsqlDbUrl)
3839
{
3940
ShouldPluralizeFromItemName = false,
4041
EndpointType = endpointType
4142
};
4243

43-
return new KSqlDBContext(ContextOptions);
44+
modelBuilder ??= new ModelBuilder();
45+
return new KSqlDBContext(ContextOptions, modelBuilder);
4446
}
4547

4648
[TestCleanup]
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
using System.Text.Json.Serialization;
2+
using FluentAssertions;
3+
using ksqlDb.RestApi.Client.FluentAPI.Builders;
4+
using ksqlDb.RestApi.Client.IntegrationTests.Helpers;
5+
using ksqlDb.RestApi.Client.IntegrationTests.Models;
6+
using ksqlDB.RestApi.Client.KSql.Linq;
7+
using ksqlDB.RestApi.Client.KSql.RestApi;
8+
using ksqlDB.RestApi.Client.KSql.RestApi.Enums;
9+
using ksqlDB.RestApi.Client.KSql.RestApi.Http;
10+
using ksqlDB.RestApi.Client.KSql.RestApi.Statements;
11+
using ksqlDB.RestApi.Client.KSql.RestApi.Statements.Properties;
12+
using Microsoft.VisualStudio.TestTools.UnitTesting;
13+
using NUnit.Framework;
14+
15+
namespace ksqlDb.RestApi.Client.IntegrationTests.KSql.Linq
16+
{
17+
public class ModelBuilderTests : Infrastructure.IntegrationTests
18+
{
19+
protected static string StreamName = nameof(ModelBuilderTests);
20+
private static readonly string TopicName = StreamName;
21+
private static KSqlDbRestApiClient kSqlDbRestApiClient = null!;
22+
private static ModelBuilder modelBuilder = null!;
23+
24+
[OneTimeSetUp]
25+
public static async Task ClassInitialize()
26+
{
27+
await InitializeDatabase();
28+
}
29+
30+
public record Tweet : Record
31+
{
32+
public int Id { get; set; }
33+
[JsonPropertyName("MESSAGE")]
34+
public string Message { get; set; } = null!;
35+
public bool IsRobot { get; set; }
36+
public double Amount { get; set; }
37+
public decimal AccountBalance { get; set; }
38+
}
39+
40+
public static readonly Tweet Tweet1 = new()
41+
{
42+
Id = 1,
43+
Message = "Hello world",
44+
IsRobot = true,
45+
Amount = 0.00042,
46+
};
47+
48+
public static readonly Tweet Tweet2 = new()
49+
{
50+
Id = 2,
51+
Message = "Wall-e",
52+
IsRobot = false,
53+
Amount = 1,
54+
};
55+
56+
protected static async Task InitializeDatabase()
57+
{
58+
modelBuilder = new ModelBuilder();
59+
modelBuilder.Entity<Tweet>()
60+
.Property(c => c.Id)
61+
.HasColumnName("TweetId");
62+
modelBuilder.Entity<Tweet>()
63+
.Property(c => c.AccountBalance)
64+
.Ignore();
65+
66+
var httpClient = new HttpClient
67+
{
68+
BaseAddress = new Uri(TestConfig.KSqlDbUrl)
69+
};
70+
kSqlDbRestApiClient = new KSqlDbRestApiClient(new HttpClientFactory(httpClient), modelBuilder);
71+
72+
var entityCreationMetadata = new EntityCreationMetadata(TopicName, 1)
73+
{
74+
EntityName = StreamName,
75+
ShouldPluralizeEntityName = false,
76+
IdentifierEscaping = IdentifierEscaping.Always
77+
};
78+
var result = await kSqlDbRestApiClient.CreateStreamAsync<Tweet>(entityCreationMetadata, true);
79+
result.IsSuccess().Should().BeTrue();
80+
81+
var insertProperties = new InsertProperties()
82+
{
83+
EntityName = StreamName,
84+
IdentifierEscaping = IdentifierEscaping.Always
85+
};
86+
result = await kSqlDbRestApiClient.InsertIntoAsync(Tweet1, insertProperties);
87+
result.IsSuccess().Should().BeTrue();
88+
89+
result = await kSqlDbRestApiClient.InsertIntoAsync(Tweet2, insertProperties);
90+
result.IsSuccess().Should().BeTrue();
91+
}
92+
93+
[OneTimeTearDown]
94+
public static async Task ClassCleanup()
95+
{
96+
var dropFromItemProperties = new DropFromItemProperties
97+
{
98+
IdentifierEscaping = IdentifierEscaping.Always,
99+
ShouldPluralizeEntityName = false,
100+
EntityName = StreamName,
101+
UseIfExistsClause = true,
102+
DeleteTopic = true,
103+
};
104+
await kSqlDbRestApiClient.DropStreamAsync<Models.Tweet>(dropFromItemProperties);
105+
}
106+
107+
[SetUp]
108+
public override void TestInitialize()
109+
{
110+
base.TestInitialize();
111+
112+
Context = CreateKSqlDbContext(ksqlDB.RestApi.Client.KSql.Query.Options.EndpointType.QueryStream, modelBuilder);
113+
}
114+
115+
protected virtual IQbservable<Tweet> QuerySource =>
116+
Context.CreatePushQuery<Tweet>($"`{StreamName}`");
117+
118+
[Test]
119+
public async Task Select()
120+
{
121+
//Arrange
122+
int expectedItemsCount = 2;
123+
124+
var source = QuerySource
125+
.ToAsyncEnumerable();
126+
127+
//Act
128+
var actualValues = await CollectActualValues(source, expectedItemsCount);
129+
130+
//Assert
131+
var expectedValues = new List<Tweet>
132+
{
133+
Tweet1, Tweet2
134+
};
135+
136+
expectedItemsCount.Should().Be(actualValues.Count);
137+
CollectionAssert.AreEqual(expectedValues, actualValues);
138+
}
139+
}
140+
}

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

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,29 @@ public void Property_IgnoreField()
6262

6363
//Assert
6464
fieldTypeBuilder.Should().NotBeNull();
65-
var entityMetadata = builder.GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
65+
var entityMetadata = ((IMetadataProvider)builder).GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
6666
entityMetadata.Should().NotBeNull();
6767
entityMetadata!.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(Payment.Description)).Ignore.Should().BeTrue();
6868
}
6969

70+
[Test]
71+
public void Property_HasColumnName()
72+
{
73+
//Arrange
74+
var columnName = "desc";
75+
76+
//Act
77+
var fieldTypeBuilder = builder.Entity<Payment>()
78+
.Property(b => b.Description)
79+
.HasColumnName(columnName);
80+
81+
//Assert
82+
fieldTypeBuilder.Should().NotBeNull();
83+
var entityMetadata = ((IMetadataProvider)builder).GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
84+
entityMetadata.Should().NotBeNull();
85+
entityMetadata!.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(Payment.Description)).ColumnName.Should().Be(columnName);
86+
}
87+
7088
[Test]
7189
public void MultiplePropertiesForSameType()
7290
{
@@ -83,7 +101,7 @@ public void MultiplePropertiesForSameType()
83101

84102
//Assert
85103
fieldTypeBuilder.Should().NotBeNull();
86-
var entityMetadata = builder.GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
104+
var entityMetadata = ((IMetadataProvider)builder).GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
87105
entityMetadata.Should().NotBeNull();
88106
entityMetadata!.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(Payment.Description)).Ignore.Should().BeTrue();
89107
entityMetadata.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(Payment.Amount)).Ignore.Should().BeTrue();
@@ -105,7 +123,7 @@ public void Property_IgnoreNestedField()
105123

106124
//Assert
107125
fieldTypeBuilder.Should().NotBeNull();
108-
var entityMetadata = builder.GetEntities().FirstOrDefault(c => c.Type == typeof(Composite));
126+
var entityMetadata = ((IMetadataProvider)builder).GetEntities().FirstOrDefault(c => c.Type == typeof(Composite));
109127
entityMetadata.Should().NotBeNull();
110128
var memberInfo = GetTitleMemberInfo();
111129
entityMetadata!.FieldsMetadata.First(c => c.MemberInfo == memberInfo).Ignore.Should().BeTrue();
@@ -121,7 +139,7 @@ public void AddConventionForDecimal()
121139
builder.AddConvention(decimalTypeConvention);
122140

123141
//Assert
124-
builder.Conventions[typeof(decimal)].Should().BeEquivalentTo(decimalTypeConvention);
142+
((IMetadataProvider)builder).Conventions[typeof(decimal)].Should().BeEquivalentTo(decimalTypeConvention);
125143
}
126144

127145
private class PaymentConfiguration : IFromItemTypeConfiguration<Payment>
@@ -143,7 +161,7 @@ public void FromItemTypeConfiguration()
143161
builder.Apply(configuration);
144162

145163
//Assert
146-
var entityMetadata = builder.GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
164+
var entityMetadata = ((IMetadataProvider)builder).GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
147165
entityMetadata.Should().NotBeNull();
148166
entityMetadata!.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(Payment.Description)).Ignore.Should().BeTrue();
149167
}
@@ -170,7 +188,7 @@ public void Decimal_ConfigurePrecisionAndScale()
170188

171189
//Assert
172190
fieldTypeBuilder.Should().NotBeNull();
173-
var entityMetadata = builder.GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
191+
var entityMetadata = ((IMetadataProvider)builder).GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
174192
entityMetadata.Should().NotBeNull();
175193
var metadata = (DecimalFieldMetadata)entityMetadata!.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(Payment.Amount));
176194

@@ -191,7 +209,7 @@ public void Header()
191209

192210
//Assert
193211
fieldTypeBuilder.Should().NotBeNull();
194-
var entityMetadata = builder.GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
212+
var entityMetadata = ((IMetadataProvider)builder).GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
195213
entityMetadata.Should().NotBeNull();
196214
var metadata = (BytesArrayFieldMetadata)entityMetadata!.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(Payment.Header));
197215

@@ -210,7 +228,7 @@ public void Headers()
210228

211229
//Assert
212230
fieldTypeBuilder.Should().NotBeNull();
213-
var entityMetadata = builder.GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
231+
var entityMetadata = ((IMetadataProvider)builder).GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
214232
entityMetadata.Should().NotBeNull();
215233
var metadata = entityMetadata!.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(Payment.Header));
216234

0 commit comments

Comments
 (0)