Conversation
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
force-pushed
the
fm/qk-breaking-prevented-query-name
branch
from
October 1, 2026 21:40
694f31e to
eaf0d0e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 ofd54de85(#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.ReplaceAliasesWithPropertyPathsonly.ParseFilterruns 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()andPreventSort(). A filter clause that uses the query name of this property, before an operator, throwsInvalidOperationException("'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, andIgnoredClauseBehaviorcontrols 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.
IgnoredClauseBehaviorcontrols the ignored clause.Example
InvalidOperationException: 'Title' is not allowed for filtering or sorting.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.
InvalidOperationExceptionis not aQueryKitException, so an API that mapsQueryKitExceptionto a400returns a500for this input.Migration
InvalidOperationExceptionto reject a filter on a fully prevented property, this exception no longer occurs. UseIgnoredClauseBehaviorto control the ignored clause.QueryKitPropertyMappings.ReplaceAliasesWithPropertyPathsdirectly, 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
IgnoredClauseBehaviorsection already says that QueryKit ignores a clause on a property that hasPreventFilter. This PR makes the query name obey that text.Interaction with other PRs
PreventFilterin more places (right side, arithmetic, property lists, custom operations). It does not touch the alias rewrite pass, so the two PRs do not overlap.ParseFilter. That PR keeps the v1.14.2 throw throughEnsureNoQueryNameOfAPropertyPreventedForFilterAndSort, which callsReplaceAliasesWithPropertyPaths. If both PRs merge, that check does nothing, and the parser ignores the prevented clause like on this PR. Delete the check then.Tests
Unit (
QueryKit.UnitTests/PropertyResolverTests.cs), from main before #134:property_prevented_for_filter_and_sort_throws_by_its_query_namebecomesproperty_prevented_for_filter_and_sort_is_removed_by_its_query_nameagain. It expectsx => (x.Age > 100).property_prevented_for_filter_and_sort_throws_by_its_query_name_before_an_operator_aliasfrom 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_rowcomes back. It usesfirst == "Paul"under an||withIgnoredClauseBehavior.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.