Skip to content
Open
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
15 changes: 15 additions & 0 deletions QueryKit.IntegrationTests/Tests/FilterParsingRegressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace QueryKit.IntegrationTests.Tests;
using System.Globalization;
using System.Linq.Expressions;
using Configuration;
using Exceptions;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Operators;
Expand Down Expand Up @@ -320,4 +321,18 @@ public async Task has_returns_matching_rows(string input, string[] expectedTitle
// Assert
recipes.Select(x => x.Title[prefix.Length..]).Should().BeEquivalentTo(expectedTitles);
}

[Fact]
public void string_operator_with_null_value_throws_querykit_exception()
{
// Arrange
var testingServiceScope = new TestingServiceScope();
var queryablePeople = testingServiceScope.DbContext().People;

// Act
var act = () => queryablePeople.ApplyQueryKitFilter("Title @= null");

// Assert
act.Should().Throw<QueryKitParsingException>();
}
}
16 changes: 16 additions & 0 deletions QueryKit.UnitTests/FilterParsingRegressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,22 @@ public void has_returns_matching_rows(string input, string[] expectedTitles)
result.Select(x => x.Title).Should().BeEquivalentTo(expectedTitles);
}

[Theory]
[InlineData("Title @= null")]
[InlineData("Title _= null")]
[InlineData("Title _-= null")]
[InlineData("Title !@= null")]
[InlineData("Title !_= null")]
[InlineData("Title !_-= null")]
[InlineData("Title @=* null")]
[InlineData("Title !_-=* null")]
public void string_operator_with_null_value_throws_querykit_exception(string input)
{
var act = () => FilterParser.ParseFilter<TestingPerson>(input);

act.Should().Throw<QueryKitParsingException>();
}

private static TResult WithCulture<TResult>(string cultureName, Func<TResult> action)
{
var originalCulture = CultureInfo.CurrentCulture;
Expand Down
20 changes: 20 additions & 0 deletions QueryKit/Operators/ComparisonOperator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,8 @@ public ContainsType(bool caseInsensitive = false, bool usesAll = false) : base("
public override bool IsCountOperator() => false;
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType, CaseInsensitiveMode caseMode = CaseInsensitiveMode.Lower)
{
EnsureStringValueIsNotNull(right, Operator());

if (left.Type.IsGenericType && left.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
return GetCollectionExpression(left, right, "Contains", false, UsesAll);
Expand Down Expand Up @@ -400,6 +402,8 @@ public StartsWithType(bool caseInsensitive = false, bool usesAll = false) : base
public override bool IsCountOperator() => false;
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType, CaseInsensitiveMode caseMode = CaseInsensitiveMode.Lower)
{
EnsureStringValueIsNotNull(right, Operator());

if (left.Type.IsGenericType && left.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
return GetCollectionExpression(left, right, "StartsWith", false, UsesAll);
Expand Down Expand Up @@ -435,6 +439,8 @@ public EndsWithType(bool caseInsensitive = false, bool usesAll = false) : base("
public override bool IsCountOperator() => false;
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType, CaseInsensitiveMode caseMode = CaseInsensitiveMode.Lower)
{
EnsureStringValueIsNotNull(right, Operator());

if (left.Type.IsGenericType && left.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
return GetCollectionExpression(left, right, "EndsWith", false, UsesAll);
Expand Down Expand Up @@ -470,6 +476,8 @@ public NotContainsType(bool caseInsensitive = false, bool usesAll = false) : bas
public override bool IsCountOperator() => false;
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType, CaseInsensitiveMode caseMode = CaseInsensitiveMode.Lower)
{
EnsureStringValueIsNotNull(right, Operator());

if (left.Type.IsGenericType && left.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
return GetCollectionExpression(left, right, "Contains", true, UsesAll);
Expand Down Expand Up @@ -505,6 +513,8 @@ public NotStartsWithType(bool caseInsensitive = false, bool usesAll = false) : b
public override bool IsCountOperator() => false;
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType, CaseInsensitiveMode caseMode = CaseInsensitiveMode.Lower)
{
EnsureStringValueIsNotNull(right, Operator());

if (left.Type.IsGenericType && left.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
return GetCollectionExpression(left, right, "StartsWith", true, UsesAll);
Expand Down Expand Up @@ -540,6 +550,8 @@ public NotEndsWithType(bool caseInsensitive = false, bool usesAll = false) : bas
public override bool IsCountOperator() => false;
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType, CaseInsensitiveMode caseMode = CaseInsensitiveMode.Lower)
{
EnsureStringValueIsNotNull(right, Operator());

if (left.Type.IsGenericType && left.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
return GetCollectionExpression(left, right, "EndsWith", true, UsesAll);
Expand Down Expand Up @@ -1087,6 +1099,14 @@ private Expression GetCountExpression(Expression left, Expression right, string
return (Expression)comparisonMethod.Invoke(null, new object[] { countExpression, right })!;
}

private static void EnsureStringValueIsNotNull(Expression right, string op)
{
if (right is ConstantExpression { Value: null })
{
throw new QueryKitParsingException($"The '{op}' operator does not accept a null value. Use '==' or '!=' to compare with null.");
}
}

private static (Expression left, Expression right) EnsureCompatibleExpressionTypes(Expression left, Expression right)
{
if (left.Type == right.Type)
Expand Down
Loading