Skip to content

Commit bc1537a

Browse files
kevin-dpsamwillis
andauthored
feat: Distinct operator for queries (#244)
Co-authored-by: Sam Willis <sam.willis@gmail.com>
1 parent d016434 commit bc1537a

File tree

7 files changed

+590
-8
lines changed

7 files changed

+590
-8
lines changed

.changeset/slick-ghosts-shine.md

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+
New distinct operator for queries.

packages/db/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"description": "A reactive client store for building super fast apps on sync",
44
"version": "0.0.21",
55
"dependencies": {
6-
"@electric-sql/d2mini": "^0.1.6",
6+
"@electric-sql/d2mini": "^0.1.7",
77
"@standard-schema/spec": "^1.0.0"
88
},
99
"devDependencies": {

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,27 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
480480
}) as any
481481
}
482482

483+
/**
484+
* Specify that the query should return distinct rows.
485+
* Deduplicates rows based on the selected columns.
486+
* @returns A QueryBuilder with distinct enabled
487+
*
488+
* @example
489+
* ```ts
490+
* // Get countries our users are from
491+
* query
492+
* .from({ users: usersCollection })
493+
* .select(({users}) => users.country)
494+
* .distinct()
495+
* ```
496+
*/
497+
distinct(): QueryBuilder<TContext> {
498+
return new BaseQueryBuilder({
499+
...this.query,
500+
distinct: true,
501+
}) as any
502+
}
503+
483504
// Helper methods
484505
private _getCurrentAliases(): Array<string> {
485506
const aliases: Array<string> = []

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { filter, map } from "@electric-sql/d2mini"
1+
import { distinct, filter, map } from "@electric-sql/d2mini"
22
import { compileExpression } from "./evaluators.js"
33
import { processJoins } from "./joins.js"
44
import { processGroupBy } from "./group-by.js"
@@ -98,8 +98,12 @@ export function compileQuery(
9898
}
9999
}
100100

101+
if (query.distinct && !query.fnSelect && !query.select) {
102+
throw new Error(`DISTINCT requires a SELECT clause.`)
103+
}
104+
101105
// Process the SELECT clause early - always create __select_results
102-
// This eliminates duplication and allows for future DISTINCT implementation
106+
// This eliminates duplication and allows for DISTINCT implementation
103107
if (query.fnSelect) {
104108
// Handle functional select - apply the function to transform the row
105109
pipeline = pipeline.pipe(
@@ -190,6 +194,11 @@ export function compileQuery(
190194
}
191195
}
192196

197+
// Process the DISTINCT clause if it exists
198+
if (query.distinct) {
199+
pipeline = pipeline.pipe(distinct(([_key, row]) => row.__select_results))
200+
}
201+
193202
// Process orderBy parameter if it exists
194203
if (query.orderBy && query.orderBy.length > 0) {
195204
const orderedPipeline = processOrderBy(

packages/db/src/query/ir.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface QueryIR {
1515
orderBy?: OrderBy
1616
limit?: Limit
1717
offset?: Offset
18+
distinct?: true
1819

1920
// Functional variants
2021
fnSelect?: (row: NamespacedRow) => any

0 commit comments

Comments
 (0)