Repository navigation
Context sweep: every translated member in a filter, a negated filter, an ordering, a grouping key, an aggregate and a projection, over required and nullable operands, against LINQ over the loaded rows #362
Description
Problem
The differential sweeps evaluate every operation in a Select projection or in raw SQL, over required columns: ShapeMatrixParityTests, and DifferentialFuzzParityTests through FuzzEngine. Their .Where calls are LINQ over the loaded results. EF Core's own rewrites act elsewhere. It simplifies on declared function nullability in a filter (#349). It compensates comparisons and negations over nullable operands (#355). It leaves NULL placement in ORDER BY to the database (#359), and the aggregates skip NULL inputs (#361). Its materializer throws on a NULL read into a non-nullable type (#355). And a filter and a projection can disagree within SQL (#360).
None of that shows in a projection over a required column. That is why #349, severity/high on every required column, was found by a consumer's probe rather than here. Probing this axis by hand over two days found #349, #350, #355, #360 and #361, and the documentation gap #359.
Change
A sweep with two new axes, on top of the shapes and operand kinds the existing sweeps vary:
- Query context:
- a projection;
Where(op), andWhere(!op)for a Boolean;Where(op == null),Where(op != null)andWhere((op ?? fallback) <cmp> v)for a nullable result;OrderBy(op)andOrderByDescending(op), eachThenBy(Id), for a comparable result;- a
GroupBy(op)key, with counts; - an aggregate input, over a group holding NULLs;
- a filter on a projected value (
Select(r => new { V = op }).Where(x => x.V == null)).
- Operand nullability: a required column; a nullable column holding NULL, in a database of its own as in
PredicatesOverANullableColumn; a captured parameter, null and not null; an inlined constant.
The members are discovered, as the probes discover types: every translated range, RangeSet and value set member. Each operation is written once and spliced onto the column (FuzzEngine.Splice). The oracle is LINQ to Objects over the loaded rows. A row whose in-memory call throws is left out, and #350's rule (c) decides which rows those are. A pairwise covering array over member × context × nullability × shape keeps the run time bounded.
Documented differences are expectations, not findings. These are NULL placement in ORDER BY (docs/ranges.md#sort-ranges, #359), CompareTo with a null (#220, #253, #256, #261), and the rows of docs/architecture.md#domain-width-and-the-boundary. The sweep reads them from one place and asserts them, so a documented difference can neither fail the sweep nor hide a new disagreement in the same row.
A static half goes beside it: a test that lists every SqlFunctionExpression the translators emit (name, nullable, argumentsPropagateNullability). It checks each against PostgreSQL's pg_proc.proisstrict, and against a probe for a NULL from non-null inputs. That is the check #349 (lower/upper) and #360 (array_cat) needed. Measured by hand on 2026-10-24, every other range function the translators emit is strict and non-null for non-null input. The range constructor and the aggregates are non-strict and declared that way.
Done when
- The sweep is registered in
test/oracles/oracles.json, with seeds it was shown to catch: fix: declare lower() and upper() NULL for a non-null range #353 reverted ([true]inBoundFunction), and A predicate over a NULL range, RangeSet or value set column is NULL in SQL, so its negation drops the row EF Core's own translations keep #355'sIS NOT NULLwrapper dropped once it exists. - It runs in CI on PostgreSQL 16 and 18. Its disagreements go through
Candidates.Verdict. - The bound sweep of test: register #349 and #350 as ExecutedQueryTests candidates #352/fix: declare lower() and upper() NULL for a non-null range #353, the nullable-column sweep of A null range answers some extensions in memory and throws on others, and the answers it gives disagree with PostgreSQL on a NULL column #350/A predicate over a NULL range, RangeSet or value set column is NULL in SQL, so its negation drops the row EF Core's own translations keep #355, and the checks Docs: ordering by LowerBound() or UpperBound() puts open-ended and empty rows where PostgreSQL puts NULLs, also on a required column #359, Value set Union is declared NULL only for a NULL operand, but array_cat isn't strict: a filter and a projection disagree on a NULL column #360 and RangeIntersectAgg returns empty over a sequence holding a null range, where range_intersect_agg skips the NULL; RangeAgg throws #361 ask for are rows of it rather than tests of their own.
Activity
CaffeinatedCoder commentedon Oct 24, 2026
One seed this sweep should be shown to catch exists now:
test/oracles/seeds/bound-declared-null-only-for-a-null-argument.patch(#353 reverted, from #372), caught byExecutedQueryTeststoday. The two translator seedsrange-predicate-over-a-null-column-is-null.patchandvalue-set-predicate-over-a-null-column-is-null.patch(from #371) are the other pair. The context sweep should catch all three once it replaces those tests.CaffeinatedCoder commentedon Oct 24, 2026
The composition axes this issue describes are swept by #375–#380 in #383: outer joins, two nullable operands (CASE, COALESCE), quantifiers and compound predicates, repeated and compiled execution, filtered aggregates, and the declared-nullability check (pg_proc and server probes per emitted function, and the filter/projection partition per translated value). Findings: #381 and #382 (new), #360 (reach measured there). The context axis this issue names beyond those (ordering, GroupBy keys, inlined constants across every member) and folding the existing #350/#355/#359 tests into one sweep are not done there; this issue stays open for them.