Skip to content

refactor: give sortEndpointStats a strict comparator - #322

Open
FrameAutomata wants to merge 1 commit into
mainfrom
fix/endpoint-sort-comparator
Open

refactor: give sortEndpointStats a strict comparator#322
FrameAutomata wants to merge 1 commit into
mainfrom
fix/endpoint-sort-comparator

Conversation

@FrameAutomata

Copy link
Copy Markdown
Collaborator

Small hygiene change found while reviewing the percentile work in #319. Not a bug fix — see the measurement below before spending review time on it.

What

sortEndpointStats built its descending order by negating the ascending comparison:

if desc {
    return !less
}
return less

For two rows that tie on the sort key, less is false in both directions, so !less returns true for both (i, j) and (j, i). That is not the strict weak ordering sort.Slice documents a requirement for, and the results are then formally unspecified.

Descending is now the same comparison with the operands swapped, which is strict in both directions:

if desc {
    i, j = j, i
}
switch orderBy {
case "count":
    return stats[i].Count < stats[j].Count
...

Identical change in sqlite/ and duckdb/ — the two copies of this function were byte-identical before and after. ClickHouse sorts in SQL and is unaffected.

Why this is hygiene and not a fix

I tried to make the old comparator actually misbehave and could not:

trials 2400
slice sizes 8 – 20,000
distinct values 1 – 100 (heavy ties)
input patterns random, ascending-modulo, descending-modulo
misordered results 0
elements lost or duplicated 0

Go's pdqsort tolerates the >= comparator in practice today. So nothing is broken, and no user-visible symptom is being fixed. What the change buys is not depending on tolerance that sort.Slice explicitly does not promise — a future Go release is free to change it.

I had initially described this as tied rows reordering between page loads. That was wrong, and the probe above is what corrected it.

No test

None can distinguish the two versions: any behaviour a test could assert holds for the old comparator too, which is exactly what the probe measured. Adding one would be coverage-shaped code that pins nothing.

Precedent

sqlite/task.repository.go already uses the strict form (> for desc, < for asc) in all four of its sort sites. This brings the two endpoint repositories in line with the pattern already in the package.

Verification

go test -race -count=1 ./app/..., the DuckDB-tagged repository suite, go vet ./app/..., gofmt — all clean.

Reasonable to close this if you'd rather not carry the churn. It is 6 lines of behaviour-preserving change against a risk that is currently theoretical.

🤖 Generated with Claude Code

`sortEndpointStats` produced its descending order by negating the
ascending comparison. For two rows that tie on the sort key that returns
true for both (i, j) and (j, i), which is not the strict weak ordering
sort.Slice documents a requirement for, so the result is formally
unspecified. Descending is now the same comparison with the operands
swapped.

This is hygiene, not a bug fix. I could not make the old comparator
misbehave: 2400 trials over slices of 8 to 20000 elements, 1 to 100
distinct values and three input patterns produced zero misordered
results and zero lost elements, so Go's pdqsort tolerates the `>=`
comparator in practice today. What the change buys is not depending on
that tolerance.

No test, because none can distinguish the two versions -- any behaviour
a test could asserts holds for the old comparator too, which is what the
probe above measured. The sibling sort in task.repository.go already
used the strict form; this brings the two endpoint repositories in line
with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@FrameAutomata FrameAutomata added ci Run CI on this PR (remove and re-add to re-validate after a push) and removed ci Run CI on this PR (remove and re-add to re-validate after a push) labels Aug 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci Run CI on this PR (remove and re-add to re-validate after a push)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant