Skip to content

fix(filter)!: ignore a fully prevented clause by its query name - #152

Open
pdevito3 wants to merge 1 commit into
mainfrom
fm/qk-breaking-prevented-query-name
Open

pdevito3 wants to merge 1 commit into
mainfrom
fm/qk-breaking-prevented-query-name

Conversation

@pdevito3

@pdevito3 pdevito3 commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

For later consideration in a major version. Do not merge now. #134 restored the v1.14.2 behavior to keep v1.x compatible (restore commit c0e9914). This PR re-applies the behavior of d54de85 (#113). The captain decides on this PR separately.

Summary

                     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);

The change is in QueryKitPropertyMappings.ReplaceAliasesWithPropertyPaths only. ParseFilter runs this pass before the parser. The pass now rewrites the query name to the member path, and the parser then ignores the prevented clause like for the member name.

v1.14.2 behavior (and main)

A property has PreventFilter() and PreventSort(). A filter clause that uses the query name of this property, before an operator, throws InvalidOperationException ("'Title' is not allowed for filtering or sorting."). A clause that uses the member name of the same property does not throw. QueryKit ignores that clause, and IgnoredClauseBehavior controls the result.

A property with only PreventFilter() does not throw by its query name. QueryKit ignores that clause.

New behavior

The query name and the member name give the same result. QueryKit ignores the clause, and the rest of the filter runs. IgnoredClauseBehavior controls the ignored clause.

Example

var config = new QueryKitConfiguration(config =>
{
    config.IgnoredClauseBehavior = IgnoredClauseBehavior.Remove;
    config.Property<TestingPerson>(x => x.Title).HasQueryName("name").PreventFilter().PreventSort();
});
FilterParser.ParseFilter<TestingPerson>("""name == "x" || Age > 100""", config);
  • v1.14.2 and main: InvalidOperationException: 'Title' is not allowed for filtering or sorting.
  • This PR: x => (x.Age > 100). The member name, Title == "x" || Age > 100, gives the same result on main and on this PR.

Justification

A prevented property must give one result for one input. On v1.14.2, the result depends on the name that the client uses, and on the sort setting of the property. The throw also gives the client a way to find the query names of prevented properties. A clause on the member name does not tell the client this. InvalidOperationException is not a QueryKitException, so an API that maps QueryKitException to a 400 returns a 500 for this input.

Migration

  • If your code catches InvalidOperationException to reject a filter on a fully prevented property, this exception no longer occurs. Use IgnoredClauseBehavior to control the ignored clause.
  • If your code calls QueryKitPropertyMappings.ReplaceAliasesWithPropertyPaths directly, it no longer throws for this query name. It returns the input with the query name replaced by the member path.

README

No change. The README does not document the throw. The IgnoredClauseBehavior section already says that QueryKit ignores a clause on a property that has PreventFilter. This PR makes the query name obey that text.

Interaction with other PRs

Tests

Unit (QueryKit.UnitTests/PropertyResolverTests.cs), from main before #134:

  • property_prevented_for_filter_and_sort_throws_by_its_query_name becomes property_prevented_for_filter_and_sort_is_removed_by_its_query_name again. It expects x => (x.Age > 100).
  • The pin property_prevented_for_filter_and_sort_throws_by_its_query_name_before_an_operator_alias from fix: restore v1.14.2 behavior for every breaking change on main #134 is removed.

Integration (QueryKit.IntegrationTests/Tests/PropertyResolverTests.cs): prevented_property_clause_by_its_query_name_under_or_does_not_return_every_row comes back. It uses first == "Paul" under an || with IgnoredClauseBehavior.Remove, and expects no rows.

dotnet test: 469 unit tests and 298 Postgres integration tests (Testcontainers) pass, 0 failures.

Rebase on main

This branch is rebased on current main. The only conflict was in the integration tests, where main added tests at the same place.

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.
@pdevito3
pdevito3 force-pushed the fm/qk-breaking-prevented-query-name branch from 694f31e to eaf0d0e Compare October 1, 2026 21:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant