From eaf0d0ebe2fbea7548454ff6e3f095eb1e134cc0 Mon Sep 17 00:00:00 2001 From: Paul DeVito Date: Thu, 1 Oct 2026 17:31:08 +0300 Subject: [PATCH] fix(filter)!: ignore a fully prevented clause by its query name v1.14.2 and main throw InvalidOperationException when a filter uses the query name of a property with PreventFilter and PreventSort. The member name of the same property gives an ignored clause. The alias rewrite pass no longer throws, so the query name gives the same ignored clause as the member name. BREAKING CHANGE: a filter clause on the query name of a property with PreventFilter and PreventSort no longer throws InvalidOperationException. IgnoredClauseBehavior controls the clause, like for the member name. QueryKitPropertyMappings.ReplaceAliasesWithPropertyPaths no longer throws for this query name. --- .../Tests/PropertyResolverTests.cs | 29 +++++++++++++++++++ QueryKit.UnitTests/PropertyResolverTests.cs | 23 ++------------- QueryKit/QueryKitPropertyMappings.cs | 5 ---- 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/QueryKit.IntegrationTests/Tests/PropertyResolverTests.cs b/QueryKit.IntegrationTests/Tests/PropertyResolverTests.cs index 432436b..04ceb36 100644 --- a/QueryKit.IntegrationTests/Tests/PropertyResolverTests.cs +++ b/QueryKit.IntegrationTests/Tests/PropertyResolverTests.cs @@ -151,6 +151,35 @@ public async Task property_is_not_prevented_by_another_property_whose_query_name people[0].Id.Should().Be(fakePerson.Id); } + [Fact] + public async Task prevented_property_clause_by_its_query_name_under_or_does_not_return_every_row() + { + // Arrange + var testingServiceScope = new TestingServiceScope(); + var title = new Faker().Lorem.Sentence(); + var fakePerson = new FakeTestingPersonBuilder() + .WithTitle(title) + .WithFirstName("Paul") + .WithAge(30) + .Build(); + await testingServiceScope.InsertAsync(fakePerson); + + var input = $"""Title == "{title}" && (first == "Paul" || Age > 100)"""; + var config = new QueryKitConfiguration(config => + { + config.IgnoredClauseBehavior = IgnoredClauseBehavior.Remove; + config.Property(x => x.FirstName!).HasQueryName("first").PreventFilter().PreventSort(); + }); + + // Act + var people = await testingServiceScope.DbContext().People + .ApplyQueryKitFilter(input, config) + .ToListAsync(); + + // Assert + people.Should().BeEmpty(); + } + [Theory] [InlineData("first-name")] [InlineData("_first")] diff --git a/QueryKit.UnitTests/PropertyResolverTests.cs b/QueryKit.UnitTests/PropertyResolverTests.cs index 040943f..a5796cf 100644 --- a/QueryKit.UnitTests/PropertyResolverTests.cs +++ b/QueryKit.UnitTests/PropertyResolverTests.cs @@ -628,7 +628,7 @@ public void query_name_in_a_value_is_replaced() } [Fact] - public void property_prevented_for_filter_and_sort_throws_by_its_query_name() + public void property_prevented_for_filter_and_sort_is_removed_by_its_query_name() { var input = """name == "x" || Age > 100"""; var config = new QueryKitConfiguration(config => @@ -637,26 +637,9 @@ public void property_prevented_for_filter_and_sort_throws_by_its_query_name() config.Property(x => x.Title!).HasQueryName("name").PreventFilter().PreventSort(); }); - var act = () => FilterParser.ParseFilter(input, config); - - act.Should().ThrowExactly() - .WithMessage("'Title' is not allowed for filtering or sorting."); - } - - [Fact] - public void property_prevented_for_filter_and_sort_throws_by_its_query_name_before_an_operator_alias() - { - var input = """name eq "x" """; - var config = new QueryKitConfiguration(config => - { - config.EqualsOperator = "eq"; - config.Property(x => x.Title!).HasQueryName("name").PreventFilter().PreventSort(); - }); - - var act = () => FilterParser.ParseFilter(input, config); + var filterExpression = FilterParser.ParseFilter(input, config); - act.Should().ThrowExactly() - .WithMessage("'Title' is not allowed for filtering or sorting."); + filterExpression.ToDisplayString().Should().Be("x => (x.Age > 100)"); } [Fact] diff --git a/QueryKit/QueryKitPropertyMappings.cs b/QueryKit/QueryKitPropertyMappings.cs index 9408114..f793e5b 100644 --- a/QueryKit/QueryKitPropertyMappings.cs +++ b/QueryKit/QueryKitPropertyMappings.cs @@ -139,11 +139,6 @@ public string ReplaceAliasesWithPropertyPaths(string input) // (e.g. the `^` in `^^` would otherwise be read as a start-of-line anchor). var regex = AliasRegexCache.Get($@"\b{Regex.Escape(queryKitPropertyInfo.QueryName!)}\b(?=\s*{Regex.Escape(op)})"); - if (queryKitPropertyInfo is { CanSort: false, CanFilter: false} && regex.IsMatch(input)) - { - throw new InvalidOperationException($"'{queryKitPropertyInfo.Name}' is not allowed for filtering or sorting."); - } - input = regex.Replace(input, propertyPath); } }