Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions QueryKit.IntegrationTests/Tests/PropertyResolverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,34 @@ public async Task prevented_property_clause_by_its_query_name_under_or_does_not_
people.Should().BeEmpty();
}

[Fact]
public async Task unknown_property_in_arithmetic_removes_the_clause_when_unknown_properties_are_allowed()
{
// Arrange
var testingServiceScope = new TestingServiceScope();
var title = new Faker().Lorem.Sentence();
var fakePerson = new FakeTestingPersonBuilder()
.WithTitle(title)
.WithAge(30)
.Build();
await testingServiceScope.InsertAsync(fakePerson);

var input = $"""Title == "{title}" && ((Nope + 1) > 3 || Age > 100)""";
var config = new QueryKitConfiguration(config =>
{
config.IgnoredClauseBehavior = IgnoredClauseBehavior.Remove;
config.AllowUnknownProperties = true;
});

// Act
var people = await testingServiceScope.DbContext().People
.ApplyQueryKitFilter(input, config)
.ToListAsync();

// Assert
people.Should().BeEmpty();
}

[Theory]
[InlineData("first-name")]
[InlineData("_first")]
Expand Down
20 changes: 9 additions & 11 deletions QueryKit.UnitTests/PropertyResolverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -967,7 +967,7 @@ public void quoted_value_with_a_dot_on_the_right_side_is_a_value(string input, s
}

[Fact]
public void unknown_property_in_arithmetic_throws_when_unknown_properties_are_allowed()
public void unknown_property_in_arithmetic_removes_the_clause_when_unknown_properties_are_allowed()
{
var input = """(Nope + 1) > 3 || Age > 100""";
var config = new QueryKitConfiguration(config =>
Expand All @@ -976,14 +976,13 @@ public void unknown_property_in_arithmetic_throws_when_unknown_properties_are_al
config.AllowUnknownProperties = true;
});

var act = () => FilterParser.ParseFilter<TestingPerson>(input, config);
var filterExpression = FilterParser.ParseFilter<TestingPerson>(input, config);

act.Should().Throw<ArgumentException>()
.WithMessage("Property 'Nope' not found on type 'TestingPerson'");
filterExpression.ToDisplayString().Should().Be("x => (x.Age > 100)");
}

[Fact]
public void unknown_property_on_the_right_side_of_arithmetic_throws_when_unknown_properties_are_allowed()
public void unknown_property_on_the_right_side_of_arithmetic_removes_the_clause_when_unknown_properties_are_allowed()
{
var input = """(Age + 0) > Nope || Title == "a" """;
var config = new QueryKitConfiguration(config =>
Expand All @@ -992,21 +991,20 @@ public void unknown_property_on_the_right_side_of_arithmetic_throws_when_unknown
config.AllowUnknownProperties = true;
});

var act = () => FilterParser.ParseFilter<TestingPerson>(input, config);
var filterExpression = FilterParser.ParseFilter<TestingPerson>(input, config);

act.Should().Throw<ArgumentException>()
.WithMessage("Property 'Nope' not found on type 'TestingPerson'");
filterExpression.ToDisplayString().Should().Be("""x => (x.Title == "a")""");
}

[Fact]
public void unknown_property_in_arithmetic_throws_an_argument_exception()
public void unknown_property_in_arithmetic_is_not_recognized()
{
var input = """(Nope + 1) > 3""";

var act = () => FilterParser.ParseFilter<TestingPerson>(input);

act.Should().Throw<ArgumentException>()
.WithMessage("Property 'Nope' not found on type 'TestingPerson'");
act.Should().Throw<UnknownFilterPropertyException>()
.WithMessage("The filter property 'Nope' was not recognized.");
}

[Theory]
Expand Down
10 changes: 8 additions & 2 deletions QueryKit/FilterParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -896,16 +896,22 @@ private static Parser<Expression> ArithmeticComparisonExprParser<T>(ParameterExp
}

// Resolves each property in an arithmetic expression to its member path.
// Returns null when a property cannot be filtered, because then the parser removes the clause.
// Returns null when a property cannot be filtered or is an allowed unknown property, because then the parser removes the clause.
private static ArithmeticExpression? ResolveArithmeticProperties(ArithmeticExpression expr, Type entityType, IQueryKitConfiguration? config)
{
switch (expr)
{
case PropertyArithmeticExpression property:
var reference = PropertyResolver.Resolve(entityType, property.PropertyPath, config);
// Arithmetic supports only members, so a derived property or a custom operation is unknown here
if (reference.Kind != PropertyReferenceKind.Member)
{
return property;
if (config?.AllowUnknownProperties == true)
{
return null;
}

throw new UnknownFilterPropertyException(reference.UnknownSegment ?? property.PropertyPath);
}

return reference.CanFilter ? new PropertyArithmeticExpression(reference.Path) : null;
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ var input = "((Revenue - Expenses) / Revenue) > 0.1";
- **Parentheses**: Use parentheses to control calculation order and group expressions
- **Entity Framework Translation**: All arithmetic expressions are translated to efficient SQL queries
- **Property-to-Property**: Can mix property references with literal values in the same expression
- **Unknown Properties**: An unknown property in an arithmetic expression throws `UnknownFilterPropertyException`, like any other filter property. When `AllowUnknownProperties` is `true`, QueryKit ignores the clause. Arithmetic supports only entity members, so a derived property or a custom operation name is unknown here

#### Examples

Expand Down
Loading