db-ivm: min()/max() aggregates return wrong results when the extreme value is falsy (0 or empty string)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 3.9k
- Forks
- 266
- Avg merge
- 1d 4h
- Merged PRs (30d)
- 55
Description
Summary
The min() and max() aggregate functions in @tanstack/db-ivm use truthiness checks when scanning grouped values:
if (!minValue || (value && value < minValue)) {
As a result, an extreme value that is falsy in JS — numeric 0, or an empty string "" for string-typed groups — is either never selected or gets overwritten by a later non-falsy value. Grouped queries return wrong aggregate results whenever the true minimum/maximum of a group happens to be 0/"".
Note: this is a static analysis finding based on reading the source on main; I did not execute a query pipeline.
Location
- File:
packages/db-ivm/src/operators/groupBy.ts - Functions:
min().reduceandmax().reduce - Consumer:
packages/db/src/query/compiler/group-by.tsline ~187 imports these fromgroupByOperatorsand compiles user-facing.groupBy(...).select({ min: ..., max: ... })queries down to them
Problem
Current implementation of min:
reduce: (values) => {
let minValue: V | undefined
for (const [value, _multiplicity] of values) {
if (!minValue || (value && value < minValue)) {
minValue = value
}
}
return minValue
}
Two independent defects:
- Falsy minimum is never selected after another value: with
values = [[5,1],[0,1]], the first iteration setsminValue = 5. For0,!minValueis false and(value && ...)short-circuits because0is falsy — so0is skipped and the result is5. - Falsy extreme is clobbered when seen first: with
values = [[0,1],[3,1]],minValuebecomes0correctly, but the next comparison!minValueevaluates!0 === true, unconditionally overwriting it with3.
max() has the mirrored defect: with values = [[-2,1],[0,1],[-1,1]] the result is -2 instead of 0; with [[0,1],[-1,1]] the result is -1 instead of 0.
The same applies to "" as the minimum of a string group ("" < any non-empty string, but both guards treat it as unset).
Trigger / Reproduction
Any compiled query whose group contains 0 (or "") as the true extreme, e.g. conceptually:
// rows with price: [10, 0, 7]
groupBy(g => g.category).select(g => ({ minPrice: min(g.price) }))
// expected minPrice = 0; current implementation returns 7
At the unit level this is directly visible in min().reduce([[5,1],[0,1]]) → 5.
Expected Behavior
Aggregates compare values by their domain ordering only; 0 and "" are valid extremes. The scan should be driven by undefined-sentinel logic, e.g.:
if (minValue === undefined || value < minValue) { minValue = value }
(with the multiplicity ignored as today), and analogously for max.
Actual Behavior
Falsy extremes are skipped or discarded, producing incorrect min/max results for affected groups.
Impact
Silently wrong numbers in user-visible live-query results — e.g. minimum price of 0 reported as the smallest positive price, or maximum balance of 0 reported as a negative value. No error or warning is produced. The recent history around min/max (Temporal NaN fix #1255/#665, string support #1120) shows this operator family is actively hardened, but the falsy-extreme case remains.
Suggested Direction
Replace the truthiness guards with explicit undefined checks in both min().reduce and max().reduce. While there, consider a regression test asserting min([5,0]) === 0, min([0,3]) === 0, and the mirrored max([-2,0,-1]) === 0 cases.
Evidence
- Quoted implementations above are from
mainat time of writing (packages/db-ivm/src/operators/groupBy.ts). - The public compiler path (
packages/db/src/query/compiler/group-by.ts) routes usermin/maxaggregates straight into these functions. - Existing issue/PR history (#665, #1255, #428, #1120) covers Temporal and string typing but not the falsy-extreme selection bug.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in packages/db-ivm/src/operators/groupBy.ts at the min().reduce and max().reduce functions, then inspect packages/db/src/query/compiler/group-by.ts to understand the public path. Add regression coverage for falsy extrema such as 0 and an empty string, and run the relevant package tests to confirm grouped min/max results are correct.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- databases
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 88/100