Skip to content

fix(filter): accept a query name that is not a plain identifier - #115

Merged
pdevito3 merged 1 commit into
mainfrom
fm/qk-queryname-regression
Sep 30, 2026
Merged

pdevito3 merged 1 commit into
mainfrom
fm/qk-queryname-regression

Conversation

@pdevito3

Copy link
Copy Markdown
Owner

Why this is warranted

A query name can be any text, for example first-name. v1.14.2 accepted it. Main reads only letters, digits, and underscores as a property name, so first-name == "Ann" throws. This PR makes the grammar match configured query names before the general identifier. Values inside quotes stay untouched.

This is regression J in the breaking-change audit. It came in with #113 (3c85253), which removed the regex pre-pass. This fix restores the v1.14.2 behavior and adds no breaking change.

Summary

 left side, property list
-  Identifier.DelimitedBy('.')
+  configured query names, longest first, case-insensitive,
+    and the next character must not be a letter, digit, '_', or '.'
+  else Identifier.DelimitedBy('.')
 arithmetic
   Identifier.DelimitedBy('.')          # unchanged: a '-' there is a minus sign
 right side, quoted values
   unchanged                            # J5 stays fixed
  • QueryKitPropertyMappings.QueryNames (internal) gives every query name of a property, a derived property, and a custom operation.
  • The static parsers stay static. Only the property-path parser and the property-list parser depend on the configuration. They are built with the other per-configuration parsers, like the operator-alias parser.
  • The verify-querykit harness has a new loose-names preset for this proof.

Evidence

Audit proof rows J1 to J8, as regression tests in QueryKit.UnitTests/PropertyResolverTests.cs:

Row Query name and input v1.14.2 main before this PR
J1 first-name == "Ann" OK throws UnknownFilterPropertyException ... 'first' x => (x.FirstName == "Ann")
J2 _first == "Ann" OK throws ParsingException x => (x.FirstName == "Ann")
J3 first name == "Ann" OK throws UnknownFilterPropertyException ... 'first' x => (x.FirstName == "Ann")
J4 person.first == "Ann" OK OK OK
J5 Title == "first-name == x" value rewritten value kept value kept
J6 Title == first "first" "first" "first"
J7 sort first-name desc OK OK OK
J8 first_name == "Ann" OK OK OK

More tests: the case of the query name is ignored, the longer name wins (first name and first), first does not match the start of FirstName, a hyphen query name works in a property list, and a derived property can have a hyphen query name. The new integration test runs J1 to J3 on Postgres.

  • Before: with the library fix set aside, 7 of the new unit tests fail (J1, J2, J3, and the related cases). J4 to J8 pass, as on main.
  • After: dotnet test passes: 359 unit tests and 271 integration tests, 0 failures.

Live proof with the verify-querykit harness, ApplyQueryKitFilter on a list and on EF Core with Postgres:

--config loose-names   (Title->recipe-title, Rating->_stars, Author.Name->chef name)

'recipe-title == "Pancakes"'
  before: UnknownFilterPropertyException: The filter property 'recipe' was not recognized.
  after:  memory ["Pancakes"], postgres ["Pancakes"], WHERE r."Title" = @Value
'_stars > 3'
  before: ParsingException: unexpected '_'; expected ( or letter
  after:  memory ["Pancakes","Salt Bread"], postgres ["Pancakes","Salt Bread"]
'chef name == "Julia Child"'
  before: UnknownFilterPropertyException: The filter property 'chef' was not recognized.
  after:  memory ["Pancakes","Salt Bread"], postgres ["Pancakes","Salt Bread"], WHERE a."Name" = @Value
'(recipe-title, chef name) @=* "julia"'
  after:  memory ["Pancakes","Salt Bread"], postgres ["Pancakes","Salt Bread"]
'Title == "recipe-title == x"'
  after:  @Value='recipe-title == x'   (the value is not rewritten)

Merge Danger

Door: two-way

The change is in the parser only. No public API or stored data changes. A revert restores main as it is now.

Blast Radius: filters

A configured query name now wins over the general identifier on the left side and in property lists. v1.14.2 also replaced a configured query name before the parse, so this order is the same as v1.14.2. In arithmetic, only identifier query names resolve, as on main. A query name with a - in arithmetic is not supported, because (a-b) can mean a minus.

A query name can be any text, for example first-name, _first, or first name. v1.14.2 accepted it, because a regex pass replaced the query name before the parse. Main reads only letters, digits, and underscores as a property name, so first-name == "Ann" throws.

The grammar now matches the configured query names before the general identifier, on the left side and in property lists. It tries the longest name first, ignores case, and needs a name boundary after the name. Values inside quotes stay untouched. Arithmetic still reads identifiers only, because a hyphen there is a minus sign.
@pdevito3
pdevito3 merged commit 63aa008 into main Sep 30, 2026
2 checks passed
@pdevito3
pdevito3 deleted the fm/qk-queryname-regression branch September 30, 2026 13:46
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