Skip to content

Commit a55e2bf

Browse files
kevin-dpclaudeautofix-ci[bot]
authored
fix(db): throw error when fn.select() is used with groupBy() (#1324)
* fix(db): throw error when fn.select() is used with groupBy() fn.select() with groupBy() silently produced wrong results ({ __key_0: ... }) because the compiler cannot statically analyze an opaque function to discover aggregate expressions needed by groupBy. This adds a clear error message directing users to use the standard .select() API instead. Closes #1189 Co-Authored-By: Claude Opus 4.6 <[email protected]> * ci: apply automated fixes * chore: add changeset for fn.select + groupBy error Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 373f62d commit a55e2bf

4 files changed

Lines changed: 46 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/db': patch
3+
---
4+
5+
fix(db): throw error when fn.select() is used with groupBy()

packages/db/src/errors.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,17 @@ export class DistinctRequiresSelectError extends QueryCompilationError {
433433
}
434434
}
435435

436+
export class FnSelectWithGroupByError extends QueryCompilationError {
437+
constructor() {
438+
super(
439+
`fn.select() cannot be used with groupBy(). ` +
440+
`groupBy requires the compiler to statically analyze aggregate functions (count, sum, max, etc.) in the SELECT clause, ` +
441+
`which is not possible with fn.select() since it is an opaque function. ` +
442+
`Use .select() instead of .fn.select() when combining with groupBy().`,
443+
)
444+
}
445+
}
446+
436447
export class HavingRequiresGroupByError extends QueryCompilationError {
437448
constructor() {
438449
super(`HAVING clause requires GROUP BY clause`)

packages/db/src/query/compiler/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
CollectionInputNotFoundError,
55
DistinctRequiresSelectError,
66
DuplicateAliasInSubqueryError,
7+
FnSelectWithGroupByError,
78
HavingRequiresGroupByError,
89
LimitOffsetRequireOrderByError,
910
UnsupportedFromTypeError,
@@ -218,6 +219,10 @@ export function compileQuery(
218219
throw new DistinctRequiresSelectError()
219220
}
220221

222+
if (query.fnSelect && query.groupBy && query.groupBy.length > 0) {
223+
throw new FnSelectWithGroupByError()
224+
}
225+
221226
// Process the SELECT clause early - always create $selected
222227
// This eliminates duplication and allows for DISTINCT implementation
223228
if (query.fnSelect) {

packages/db/tests/query/group-by.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2167,6 +2167,31 @@ function createGroupByTests(autoIndex: `off` | `eager`): void {
21672167
expect(result?.totalAmount).toBe(700)
21682168
})
21692169
})
2170+
2171+
describe(`fn.select with groupBy throws error`, () => {
2172+
let ordersCollection: ReturnType<typeof createOrdersCollection>
2173+
2174+
beforeEach(() => {
2175+
ordersCollection = createOrdersCollection(autoIndex)
2176+
})
2177+
2178+
test(`fn.select with groupBy should throw FnSelectWithGroupByError`, () => {
2179+
expect(() =>
2180+
createLiveQueryCollection({
2181+
startSync: true,
2182+
query: (q) =>
2183+
q
2184+
.from({ orders: ordersCollection })
2185+
.groupBy(({ orders }) => orders.customer_id)
2186+
.fn.select((row) => ({
2187+
customerId: row.orders.customer_id,
2188+
totalAmount: sum(row.orders.amount),
2189+
orderCount: count(row.orders.id),
2190+
})),
2191+
}),
2192+
).toThrow(`fn.select() cannot be used with groupBy()`)
2193+
})
2194+
})
21702195
})
21712196
}
21722197

0 commit comments

Comments
 (0)