Skip to content

A filter narrows the query, not the page that was taken - #101

Merged
kisielewski merged 1 commit into
mainfrom
fix/a-filter-narrows-the-query-not-the-page
Sep 8, 2026
Merged

A filter narrows the query, not the page that was taken#101
kisielewski merged 1 commit into
mainfrom
fix/a-filter-narrows-the-query-not-the-page

Conversation

@kisielewski

Copy link
Copy Markdown
Member

Filtering the manager's submissions list by state or verdict answered with an
empty first page and the match on the second. Reported from production on
2026-09-08: one submission matched, and page one said nothing matches the
filters
.

What was wrong

ManagerReadService.ListSubmissionsAsync took the page from the database and
then filtered it in memory:

var total = await query.CountAsync(ct);
var page = await query.OrderByDescending().Skip().Take().ToListAsync(ct);

// State and verdict live on the newest job, which EF cannot filter
// on without loading it — so they are applied here, after paging,
// and the count above is of what matched the rest.
if (state is not null) items = items.Where(s => s.State == state).ToList();

So a filter answered with whichever matches happened to fall on the page that
was asked for
. Two consequences, both visible in the report:

  • a single match sitting beyond the first page left that page empty, and
  • total counted rows the filter would have removed, so the pager offered pages
    that were empty by construction.

The comment's premise is what made it look reasonable, and it is not true: EF
Core translates the correlated subquery this needs. ListRunnersAsync, twenty
lines below, already filters its state in the query — this listing was the
outlier.

What changed

State and verdict narrow the query, before CountAsync and before paging. The
subquery makes the same choice Scoring.Current does — the highest attempt
number — and says so, because a row filtered on one attempt and rendered from
another would be a subtler version of this bug.

Two behaviours are kept deliberately: a submission with no job at all still reads
as queued, which is what Project reports for one; and a state the product has
no name for now matches nothing rather than being read as the default, which
would answer a question nobody asked.

Verified

dotnet build, zero warnings. Three new tests in PageQueryTests.cs, run
against the Testcontainers PostgreSQL the suite uses:

  • a match that falls beyond page one is on page one once filtered, and total
    is the filtered count;
  • the state filter counts and pages what matched, and a state nothing is in
    answers empty;
  • an unrecognised state matches nothing.

Sabotaged to prove they bite: with the filters put back after paging, all
three fail — the first on Assert.Single against an empty page, which is the
screen the report came in about.

That the tests pass at all is also the evidence that the subquery translates: an
untranslatable one throws rather than returning the wrong rows.

State and verdict were applied to the page after the database had produced
it, so a match beyond the first page left that page empty and `total` counted
rows the filter removed. Both now narrow the query.
@kisielewski
kisielewski merged commit 934ab85 into main Sep 8, 2026
3 checks passed
@kisielewski
kisielewski deleted the fix/a-filter-narrows-the-query-not-the-page branch September 8, 2026 21:34
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