tomasfabian / ksqlDB.RestApi.Client-DotNet

ksqlDb.RestApi.Client is a C# LINQ-enabled client API for issuing and consuming ksqlDB push and pull queries and executing statements.
MIT License
94 stars 25 forks source link

LINQ Where ignores JsonPropertyNameAttribute when using new for column selection, causing failing ksqldb request. #68

Closed mrt181 closed 6 months ago

mrt181 commented 6 months ago

Describe the bug When defining a query like this:

    dbContext.CreatePullQuery<SubProperty>()
      .Select(c => new { c.Property })
      .Where(c => c.Property == "test")
      .ToQueryString();

Where SubProperty is defined like this

  [Struct]
  public class SubProperty
  {
    [JsonPropertyName("prop")] public string Property { get; set; } = null!;
  }

The create query string uses the wrong column name Property instead of prop.

To Reproduce Add this unit test to ksqlDb.RestApi.Client.Tests.KSql.Linq.PullQueries.PullQueryExtensionsTests

  [TestCase(Never, ExpectedResult = $"SELECT prop FROM {nameof(SubProperty)}\nWHERE prop == 'test';")]
  [TestCase(Keywords, ExpectedResult = $"SELECT prop FROM {nameof(SubProperty)}\nWHERE prop == 'test';")]
  [TestCase(Always, ExpectedResult = $"SELECT `prop` FROM `{nameof(SubProperty)}`\nWHERE `prop` == 'test';")]
  public string SelectColumnsUsingPullQueryThatHaveJsonPropertyNameAndWhere(IdentifierEscaping escaping)
  {
    //Arrange
    var dbContext = new KSqlDBContext(new KSqlDBContextOptions(TestParameters.KsqlDbUrl)
      { IdentifierEscaping = escaping , ShouldPluralizeFromItemName = false});

    //Act
    var ksql = dbContext.CreatePullQuery<SubProperty>()
      .Select(c => new { c.Property })
      .Where(c => c.Property == "test")
      .ToQueryString();

    //Assert
    return ksql.ReplaceLineEndings();

Expected behavior The returned strings should be:

SELECT prop FROM SubProperty
WHERE prop == 'test';
SELECT prop FROM SubProperty
WHERE prop == 'test';
SELECT `prop` FROM `SubProperty`
WHERE `prop` == 'test';

But instead they are

SELECT prop FROM SubProperty
WHERE Property == 'test';
SELECT prop FROM SubProperty
WHERE Property == 'test';
SELECT `prop` FROM `SubProperty`
WHERE `Property` == 'test';

Environment (please complete the following information):

Additional context For the new expression in the SELECT the attribute information from the property is used (see KSqlVisitor#ProcessVisitNewMember), but in the WHERE expression the Property from the dynamic new type is used which does not have those attributes. The whereClauses need to carry the original members in case a new expression is used and check if those have any relevant attributes.

tomasfabian commented 6 months ago

Hi @mrt181, thank you for the detailed explanation. I have submitted a hot-fix pull request. Could you please review it?

tomasfabian commented 6 months ago

Just for the record, positioning the SELECT clause last facilitates more advanced filtering capabilities. The query provided below functions correctly in the current version 4.0.0:

var ksql = dbContext.CreatePullQuery<SubProperty>()
  .Where(c => c.Property == "test")
  .Select(c => new { c.Property })
  .ToQueryString();

In C# LINQ, it's important to note that the positioning of the FROM, GROUPBY, and SELECT clauses differs from SQL statements.