Skip to content

fix(operators)!: pass usesAll through the operator factories - #146

Merged
pdevito3 merged 1 commit into
v2from
fm/qk-breaking-factory-uses-all
Oct 2, 2026
Merged

pdevito3 merged 1 commit into
v2from
fm/qk-breaking-factory-uses-all

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 factories to keep v1.x compatible (restore commit 0761e53). This PR re-applies c948532. The captain decides on this PR separately.

Summary

The 24 public ComparisonOperator factories pass usesAll to the operator:

-    public static ComparisonOperator EqualsOperator(bool caseInsensitive = false, bool usesAll = false) => new EqualsType(caseInsensitive);
+    public static ComparisonOperator EqualsOperator(bool caseInsensitive = false, bool usesAll = false) => new EqualsType(caseInsensitive, usesAll);

The same change applies to the other 23 factories (NotEqualsOperator through DoesNotHaveOperator). The change is in QueryKit/Operators/ComparisonOperator.cs only.

v1.14.2 behavior (and main)

Each factory takes a usesAll argument but does not use it. The operator always has UsesAll false. On a collection, the operator builds an Any expression, also with usesAll: true.

New behavior

The operator has the UsesAll value of the argument. With usesAll: true, the operator builds an All expression on a collection. This is the same expression as the % prefix in a filter string (for example, %==).

Example

Expression<Func<Recipe, IEnumerable<string>>> ingredientNames = x => x.Ingredients.Select(y => y.Name);
var body = ComparisonOperator.EqualsOperator(usesAll: true)
    .GetExpression<Recipe>(ingredientNames.Body, Expression.Constant("waffle"), null);
  • v1.14.2 and main: the expression is the same as Ingredients.Name == "waffle" (Any). A recipe with one "waffle" ingredient and one other ingredient matches.
  • This PR: the expression is the same as Ingredients.Name %== "waffle" (All). Only a recipe where every ingredient is "waffle" matches.

Justification

A public argument that the method ignores is a defect. A caller who passes usesAll: true asks for All and gets Any with no warning.

Migration

If you call a factory with usesAll: true and need Any, pass usesAll: false (or omit the argument). Calls that omit usesAll do not change. Filter strings do not change. The parser uses GetByOperatorString, which already passes usesAll. The library calls the factories only with the default usesAll: false.

README

No change. The README does not document these factories.

Tests

These tests come back from main before #134:

  • Unit FilterParsingRegressionTests.comparison_operator_factory_ignores_uses_all becomes comparison_operator_factory_keeps_uses_all (theory, 24 factories). UsesAll is true.
  • Unit FilterParsingRegressionTests.comparison_operator_factory_with_uses_all_builds_any_expression becomes comparison_operator_factory_with_uses_all_builds_all_expression. The expected expression is the one for %==.
  • Integration FilterParsingRegressionTests.comparison_operator_factory_with_uses_all_matches_any_item becomes comparison_operator_factory_with_uses_all_matches_every_item (Postgres). Only the recipe where every ingredient matches comes back.

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

Rebase on main

This branch is rebased on current main (#169). The rebase had no conflicts. The breaking change did not change.

@pdevito3
pdevito3 force-pushed the fm/qk-breaking-factory-uses-all branch from e266881 to 90a8b5f Compare October 1, 2026 21:48
The public ComparisonOperator factories (EqualsOperator, ContainsOperator, InOperator, and the other 21) took a usesAll argument but did not pass it to the operator. An operator from a factory always used Any on a collection. Pass usesAll to the operator, so that usesAll: true builds an All expression, like the % prefix in a filter.

BREAKING CHANGE: a ComparisonOperator from a public factory with usesAll: true now has UsesAll true and builds an All expression on a collection. Before, it built an Any expression. Code that passed usesAll: true and expected Any must pass usesAll: false.
@pdevito3
pdevito3 force-pushed the fm/qk-breaking-factory-uses-all branch from 90a8b5f to 15d4a1c Compare October 2, 2026 20:03
@pdevito3
pdevito3 changed the base branch from main to v2 October 2, 2026 20:03
@pdevito3
pdevito3 merged commit 4fd0a84 into v2 Oct 2, 2026
2 checks passed
@pdevito3
pdevito3 deleted the fm/qk-breaking-factory-uses-all branch October 2, 2026 20:05
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