Skip to content

fix(operators)!: reject a null value for case-sensitive string operators - #117

Open
pdevito3 wants to merge 1 commit into
mainfrom
fm/qk-breaking-null-string-value
Open

pdevito3 wants to merge 1 commit into
mainfrom
fm/qk-breaking-null-string-value

Conversation

@pdevito3

Copy link
Copy Markdown
Owner

Summary

This PR makes a null value with a case-sensitive string operator throw QueryKitParsingException. This is a breaking change. It is one of the six breaking changes that were removed from #114 so that main stays compatible with v1.14.2.

Status: for later consideration. Do not merge this PR now. The captain decides on each breaking change separately. If this change is accepted, it needs a major version.

Main already has the null checks on the property (left != null && ...). This PR adds only EnsureStringValueIsNotNull and its six calls.

Old behavior (v1.14.2 and main)

Title @= null builds x.Title.Contains(null).

  • In memory, string.Contains throws ArgumentNullException when the query is enumerated.
  • In a database, EF translates the expression and the query returns no rows.

The same is true for _=, _-=, and their negations.

New behavior

A null value on the right side of @=, _=, _-=, !@=, !_=, or !_-= throws QueryKitParsingException when the filter is parsed. The case-insensitive forms (@=* and more) also throw. This occurs before the query runs, for both memory and database queries.

Example

Directions @= null

Proof from the verify-querykit harness:

Target main This PR
memory ArgumentNullException QueryKitParsingException: "The '@=' operator does not accept a null value. Use '==' or '!=' to compare with null."
Postgres 0 rows (WHERE FALSE) the same QueryKitParsingException

A null property still works with a string value. Directions !@= "bake" returns Pancakes, Beef Stew, and Plain Water (null Directions) on both targets.

Justification

"Contains null" has no meaning, and v1.14.2 gives two different answers for it. The in-memory answer is an exception type that a consumer does not expect from a parser. The database answer is a silent empty result.

A client that sends this filter has an error, and the API must tell the client. A QueryKitParsingException at parse time lets the API return a 400 with a clear message. This is the same path as every other bad filter. The break is small: only a consumer that depends on the empty database result sees a change.

Migration

Use == or != to compare with null.

Tests

  • New unit theory: string_operator_with_null_value_throws_querykit_exception, with 8 cases.
  • New integration test: string_operator_with_null_value_throws_querykit_exception.
  • dotnet test: 354 unit tests and 269 integration tests pass.

Title @= null built x.Title.Contains(null). In memory, the query threw ArgumentNullException when it ran. On Postgres, the query returned no rows. The same was true for _=, _-=, and their negations.

The operators now throw QueryKitParsingException when the filter is parsed, on every target.

BREAKING CHANGE: a null value on the right side of @=, _=, _-=, !@=, !_=, or !_-= (and their case-insensitive forms) throws QueryKitParsingException from ApplyQueryKitFilter. A database query with this filter returned no rows before. Use == or != to compare with null.
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