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
4 changes: 2 additions & 2 deletions QueryKit.UnitTests/PropertyResolverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ public void prevented_property_on_the_right_side_of_arithmetic_is_still_filtered
}

[Fact]
public void arithmetic_property_skips_max_property_depth()
public void arithmetic_property_obeys_max_property_depth()
{
var input = """(Recipe.Rating + 0) > 1""";
var config = new QueryKitConfiguration(config =>
Expand All @@ -230,7 +230,7 @@ public void arithmetic_property_skips_max_property_depth()

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

act.Should().NotThrow();
act.Should().Throw<QueryKitPropertyDepthExceededException>();
}

[Fact]
Expand Down
31 changes: 29 additions & 2 deletions QueryKit/FilterParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -807,14 +807,41 @@ private static Parser<Expression> ArithmeticComparisonExprParser<T>(ParameterExp
.SelectMany(temp => parenthesizedArithmetic.Or(rightSideValueParser.Select(value => CreateArithmeticFromValue(value.Value))), (temp, rightSide) => new { temp.leftArithmetic, temp.op, rightSide })
.Select(temp =>
{
var leftExpr = temp.leftArithmetic.ToLinqExpression(parameter, typeof(T));
var rightExpr = temp.rightSide.ToLinqExpression(parameter, typeof(T));
var leftArithmetic = ResolveArithmeticProperties(temp.leftArithmetic, typeof(T), config);
var rightArithmetic = ResolveArithmeticProperties(temp.rightSide, typeof(T), config);

var leftExpr = leftArithmetic.ToLinqExpression(parameter, typeof(T));
var rightExpr = rightArithmetic.ToLinqExpression(parameter, typeof(T));

var (leftCompatible, rightCompatible) = EnsureCompatibleTypes(leftExpr, rightExpr);
return temp.op.GetExpression<T>(leftCompatible, rightCompatible, config?.DbContextType);
});
}

// Resolves each property in an arithmetic expression to its member path.
private static ArithmeticExpression ResolveArithmeticProperties(ArithmeticExpression expr, Type entityType, IQueryKitConfiguration? config)
{
switch (expr)
{
case PropertyArithmeticExpression property:
var reference = PropertyResolver.Resolve(entityType, property.PropertyPath, config);
if (reference.Kind != PropertyReferenceKind.Member)
{
return property;
}

return new PropertyArithmeticExpression(reference.Path);
case BinaryArithmeticExpression binary:
var left = ResolveArithmeticProperties(binary.Left, entityType, config);
var right = ResolveArithmeticProperties(binary.Right, entityType, config);
return new BinaryArithmeticExpression(left, binary.Operator, right);
case GroupedArithmeticExpression grouped:
return new GroupedArithmeticExpression(ResolveArithmeticProperties(grouped.Inner, entityType, config));
default:
return expr;
}
}

private static bool ContainsArithmeticOperator(ArithmeticExpression expr)
{
return expr switch
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,8 @@ var config = new QueryKitConfiguration(config =>

Setting `MaxPropertyDepth = 0` only allows root-level properties. A `null` value (default) allows unlimited depth.

The limit applies to every property path in a filter, also to a property path inside an arithmetic expression. For example, `(Author.Rating + 0) > 1` throws when `MaxPropertyDepth = 0`.

#### Parameterize Filter Values

By default (`ParameterizeFilterValues = false`), QueryKit writes each filter value into the SQL as a literal constant. Set `ParameterizeFilterValues` to `true` to send filter values as SQL parameters instead. Parameters let EF Core reuse one compiled query and one database plan across calls that differ only in their filter values.
Expand Down
Loading