Skip to content

fix(filter)!: resolve query names in the grammar again - #154

Merged
pdevito3 merged 2 commits into
v2from
fm/qk-breaking-query-names
Oct 3, 2026
Merged

pdevito3 merged 2 commits into
v2from
fm/qk-breaking-query-names

Conversation

@pdevito3

@pdevito3 pdevito3 commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • ParseFilter no longer rewrites query names in the filter text before the parse. The operator and logical alias passes stay.
  • PropertyPathParser(config) reads a configured query name first (longest first), then a normal identifier path. The left side of a comparison and a property list use this parser.
  • PropertyResolver.Resolve maps a query name to the property path of its mapping before the depth check and the member lookup. Arithmetic and property lists use Resolve, so a query name works there too.
  • Each name maps one time. SortParser passes the raw sort name to Resolve. The right side of a comparison uses ResolveWithoutQueryName, because a query name on the right side is a value. The HasConversion check uses the resolved mapping.
  • Every query name ignores case with the rules of the invariant culture. The current-culture lookup (GetPropertyInfoByQueryNameInCurrentCulture) is deleted.
  • The regex check EnsureNoQueryNameOfAPropertyPreventedForFilterAndSort is deleted. A property with PreventFilter() and PreventSort() is now removed by its query name like any prevented property. It does not throw InvalidOperationException.

v1.14.2 behavior

QueryKit replaced each query name in front of an operator with its property path, on the raw text, before the parse. As a result:

  • A query name inside a quoted value changed. Title == "first == x" compared Title with FirstName == x.
  • A query name in a property list threw UnknownFilterPropertyException.
  • A query name in arithmetic threw ArgumentException.
  • A query name after a dot changed too. Author.name == "Lee" became Author.Title.
  • A query name matched with the case rules of the current culture. In tr-TR, TIPALPHA did not match the query name tipalpha.

New behavior

HasQueryName("first") on FirstName, and HasQueryName("stars") on Rating:

Input v1.14.2 This PR
Title == "first == x" x => (x.Title == "FirstName == x") x => (x.Title == "first == x")
(first, LastName) == "Paul" UnknownFilterPropertyException: 'first' x => ((x.FirstName == "Paul") OrElse (x.LastName == "Paul"))
(stars + 0) > 3 ArgumentException filters by Rating
STARS > 3 in tr-TR UnknownFilterPropertyException filters by Rating

#132 bypass with #136

With #136 and the first commit of this PR together, a name could map two times. One time came from the query name, and one time came from Resolve:

Input Configuration Before the fix This PR
Directions == Title Directions query name title, Title PreventFilter x.Directions == x.Directions clause ignored
Directions _= Title same x.Directions.StartsWith(x.Directions) clause ignored
sort title asc Rating query name directions, Directions query name title with PreventSort sorted by Rating sort skipped

Migration

  • If a client sent a query name in another letter case and relied on the current culture (for example tr-TR), the name now matches with the invariant rules.
  • If a client caught InvalidOperationException for a query name of a property with PreventFilter() and PreventSort(), the clause is now removed (or true == true with IgnoredClauseBehavior.ReplaceWithTrue).

Tests

  • Unit PropertyResolverTests: prevented_property_on_the_right_side_is_not_compared_when_another_query_name_matches_its_name (== and _=), prevented_sort_property_is_skipped_when_its_property_path_is_another_query_name, and the two property_prevented_for_filter_and_sort_is_removed_by_its_query_name* tests (they expected a throw before).
  • Unit AliasCultureTests: I/i matches in tr-TR and en-US in a comparison, a property list, and a sort. ı and İ do not match I and i.
  • Integration (Postgres): prevented_property_on_the_right_side_is_not_compared_when_another_query_name_matches_its_name, query_name_in_a_property_list_is_filtered, query_name_in_arithmetic_is_filtered, and query_name_with_i_matches_its_upper_case_in_tr_tr.
  • dotnet test: 527 unit tests and 324 Postgres integration tests pass.

@pdevito3
pdevito3 force-pushed the fm/qk-breaking-query-names branch from e5dbe6b to b8b4ae9 Compare October 1, 2026 20:59
pdevito3 added a commit that referenced this pull request Oct 1, 2026
The public method ReplaceAliasesWithPropertyPaths does not replace a query
name after a dot anymore. A query name after a dot is a segment of a nested
path of another type. For example, with the query name "name" on Title,
"Author.Name" stays "Author.Name".

The filter parser calls an internal overload that keeps the v1.14.2 match.
Filters do not change. #154 owns
the filter change for a query name after a dot.

BREAKING CHANGE: ReplaceAliasesWithPropertyPaths keeps a query name after a
dot. It also does not throw InvalidOperationException for a fully prevented
query name after a dot.
@pdevito3
pdevito3 force-pushed the fm/qk-breaking-query-names branch from b8b4ae9 to 8b21edc Compare October 1, 2026 21:44
pdevito3 added a commit that referenced this pull request Oct 1, 2026
The public method ReplaceAliasesWithPropertyPaths does not replace a query
name after a dot anymore. A query name after a dot is a segment of a nested
path of another type. For example, with the query name "name" on Title,
"Author.Name" stays "Author.Name".

The filter parser calls an internal overload that keeps the v1.14.2 match.
Filters do not change. #154 owns
the filter change for a query name after a dot.

BREAKING CHANGE: ReplaceAliasesWithPropertyPaths keeps a query name after a
dot. It also does not throw InvalidOperationException for a fully prevented
query name after a dot.
@pdevito3
pdevito3 changed the base branch from main to v2 October 3, 2026 11:44
QueryKit no longer replaces query names in the filter text before the parse. The grammar reads a query name as a property, and the property resolver maps it to its property path. As a result, a query name works in a property list and in arithmetic, and text inside a quoted value does not change.

A property query name matches with the case rules of the current culture, like the alias regex of v1.14.2. A derived property or custom operation query name still ignores case with the invariant rules. When the grammar reads such a query name where v1.14.2 read an unknown identifier path, a filter that fails still throws the v1.14.2 exception.

A property list skips a property that has PreventFilter by its query name too. Every configured property has its name as its default query name, so a property list also skips a prevented property in another letter case.

A property that has PreventFilter and PreventSort still throws InvalidOperationException by its query name in front of an operator.

BREAKING CHANGE: a query name inside a quoted value is not replaced anymore. A query name after a dot is not replaced anymore. A query name in a property list or in arithmetic resolves to its property instead of throwing. A property list skips a prevented property in another letter case.
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