Skip to content

Commit 8c685db

Browse files
KyleAMathewssamwilliskevin-dp
committed
Handle pushed down predicates in query collection (#681)
* Handle pushed down predicates in query collection Co-authored-by: Kevin De Porre <[email protected]> Co-authored-by: Sam Willis <[email protected]> * Deduplicate loadSubset requests in Query collection * Unit test for deduplicating limited ordered queries in query collection * GC tanstack query when subscription is unsubscribed --------- Co-authored-by: Sam Willis <[email protected]> Co-authored-by: Kevin De Porre <[email protected]>
1 parent 5283d0a commit 8c685db

File tree

15 files changed

+1596
-202
lines changed

15 files changed

+1596
-202
lines changed

.changeset/silent-trains-tell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@tanstack/query-db-collection": patch
3+
---
4+
5+
Handle pushed-down predicates
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
---
2+
id: DeduplicatedLoadSubset
3+
title: DeduplicatedLoadSubset
4+
---
5+
6+
# Class: DeduplicatedLoadSubset
7+
8+
Defined in: [packages/db/src/query/subset-dedupe.ts:34](https://github.com/TanStack/db/blob/main/packages/db/src/query/subset-dedupe.ts#L34)
9+
10+
Deduplicated wrapper for a loadSubset function.
11+
Tracks what data has been loaded and avoids redundant calls by applying
12+
subset logic to predicates.
13+
14+
## Param
15+
16+
The options for the DeduplicatedLoadSubset
17+
18+
## Param
19+
20+
The underlying loadSubset function to wrap
21+
22+
## Param
23+
24+
An optional callback function that is invoked when a loadSubset call is deduplicated.
25+
If the call is deduplicated because the requested data is being loaded by an inflight request,
26+
then this callback is invoked when the inflight request completes successfully and the data is fully loaded.
27+
This callback is useful if you need to track rows per query, in which case you can't ignore deduplicated calls
28+
because you need to know which rows were loaded for each query.
29+
30+
## Example
31+
32+
```ts
33+
const dedupe = new DeduplicatedLoadSubset({ loadSubset: myLoadSubset, onDeduplicate: (opts) => console.log(`Call was deduplicated:`, opts) })
34+
35+
// First call - fetches data
36+
await dedupe.loadSubset({ where: gt(ref('age'), val(10)) })
37+
38+
// Second call - subset of first, returns true immediately
39+
await dedupe.loadSubset({ where: gt(ref('age'), val(20)) })
40+
41+
// Clear state to start fresh
42+
dedupe.reset()
43+
```
44+
45+
## Constructors
46+
47+
### Constructor
48+
49+
```ts
50+
new DeduplicatedLoadSubset(opts): DeduplicatedLoadSubset;
51+
```
52+
53+
Defined in: [packages/db/src/query/subset-dedupe.ts:67](https://github.com/TanStack/db/blob/main/packages/db/src/query/subset-dedupe.ts#L67)
54+
55+
#### Parameters
56+
57+
##### opts
58+
59+
###### loadSubset
60+
61+
(`options`) => `true` \| `Promise`\<`void`\>
62+
63+
###### onDeduplicate?
64+
65+
(`options`) => `void`
66+
67+
#### Returns
68+
69+
`DeduplicatedLoadSubset`
70+
71+
## Methods
72+
73+
### loadSubset()
74+
75+
```ts
76+
loadSubset(options): true | Promise<void>;
77+
```
78+
79+
Defined in: [packages/db/src/query/subset-dedupe.ts:85](https://github.com/TanStack/db/blob/main/packages/db/src/query/subset-dedupe.ts#L85)
80+
81+
Load a subset of data, with automatic deduplication based on previously
82+
loaded predicates and in-flight requests.
83+
84+
This method is auto-bound, so it can be safely passed as a callback without
85+
losing its `this` context (e.g., `loadSubset: dedupe.loadSubset` in a sync config).
86+
87+
#### Parameters
88+
89+
##### options
90+
91+
[`LoadSubsetOptions`](../../type-aliases/LoadSubsetOptions.md)
92+
93+
The predicate options (where, orderBy, limit)
94+
95+
#### Returns
96+
97+
`true` \| `Promise`\<`void`\>
98+
99+
true if data is already loaded, or a Promise that resolves when data is loaded
100+
101+
***
102+
103+
### reset()
104+
105+
```ts
106+
reset(): void;
107+
```
108+
109+
Defined in: [packages/db/src/query/subset-dedupe.ts:198](https://github.com/TanStack/db/blob/main/packages/db/src/query/subset-dedupe.ts#L198)
110+
111+
Reset all tracking state.
112+
Clears the history of loaded predicates and in-flight calls.
113+
Use this when you want to start fresh, for example after clearing the underlying data store.
114+
115+
Note: Any in-flight requests will still complete, but they will not update the tracking
116+
state after the reset. This prevents old requests from repopulating cleared state.
117+
118+
#### Returns
119+
120+
`void`
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
id: isLimitSubset
3+
title: isLimitSubset
4+
---
5+
6+
# Function: isLimitSubset()
7+
8+
```ts
9+
function isLimitSubset(subset, superset): boolean;
10+
```
11+
12+
Defined in: [packages/db/src/query/predicate-utils.ts:768](https://github.com/TanStack/db/blob/main/packages/db/src/query/predicate-utils.ts#L768)
13+
14+
Check if one limit is a subset of another.
15+
Returns true if the subset limit requirements are satisfied by the superset limit.
16+
17+
## Parameters
18+
19+
### subset
20+
21+
The limit requirement to check
22+
23+
`number` | `undefined`
24+
25+
### superset
26+
27+
The limit that might satisfy the requirement
28+
29+
`number` | `undefined`
30+
31+
## Returns
32+
33+
`boolean`
34+
35+
true if subset is satisfied by superset
36+
37+
## Example
38+
39+
```ts
40+
isLimitSubset(10, 20) // true (requesting 10 items when 20 are available)
41+
isLimitSubset(20, 10) // false (requesting 20 items when only 10 are available)
42+
isLimitSubset(10, undefined) // true (requesting 10 items when unlimited are available)
43+
```
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
id: isOrderBySubset
3+
title: isOrderBySubset
4+
---
5+
6+
# Function: isOrderBySubset()
7+
8+
```ts
9+
function isOrderBySubset(subset, superset): boolean;
10+
```
11+
12+
Defined in: [packages/db/src/query/predicate-utils.ts:713](https://github.com/TanStack/db/blob/main/packages/db/src/query/predicate-utils.ts#L713)
13+
14+
Check if one orderBy clause is a subset of another.
15+
Returns true if the subset ordering requirements are satisfied by the superset ordering.
16+
17+
## Parameters
18+
19+
### subset
20+
21+
The ordering requirements to check
22+
23+
[`OrderBy`](../../@tanstack/namespaces/IR/type-aliases/OrderBy.md) | `undefined`
24+
25+
### superset
26+
27+
The ordering that might satisfy the requirements
28+
29+
[`OrderBy`](../../@tanstack/namespaces/IR/type-aliases/OrderBy.md) | `undefined`
30+
31+
## Returns
32+
33+
`boolean`
34+
35+
true if subset is satisfied by superset
36+
37+
## Example
38+
39+
```ts
40+
// Subset is prefix of superset
41+
isOrderBySubset([{expr: age, asc}], [{expr: age, asc}, {expr: name, desc}]) // true
42+
```
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
id: isPredicateSubset
3+
title: isPredicateSubset
4+
---
5+
6+
# Function: isPredicateSubset()
7+
8+
```ts
9+
function isPredicateSubset(subset, superset): boolean;
10+
```
11+
12+
Defined in: [packages/db/src/query/predicate-utils.ts:801](https://github.com/TanStack/db/blob/main/packages/db/src/query/predicate-utils.ts#L801)
13+
14+
Check if one predicate (where + orderBy + limit) is a subset of another.
15+
Returns true if all aspects of the subset predicate are satisfied by the superset.
16+
17+
## Parameters
18+
19+
### subset
20+
21+
[`LoadSubsetOptions`](../../type-aliases/LoadSubsetOptions.md)
22+
23+
The predicate requirements to check
24+
25+
### superset
26+
27+
[`LoadSubsetOptions`](../../type-aliases/LoadSubsetOptions.md)
28+
29+
The predicate that might satisfy the requirements
30+
31+
## Returns
32+
33+
`boolean`
34+
35+
true if subset is satisfied by superset
36+
37+
## Example
38+
39+
```ts
40+
isPredicateSubset(
41+
{ where: gt(ref('age'), val(20)), limit: 10 },
42+
{ where: gt(ref('age'), val(10)), limit: 20 }
43+
) // true
44+
```
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
id: isWhereSubset
3+
title: isWhereSubset
4+
---
5+
6+
# Function: isWhereSubset()
7+
8+
```ts
9+
function isWhereSubset(subset, superset): boolean;
10+
```
11+
12+
Defined in: [packages/db/src/query/predicate-utils.ts:21](https://github.com/TanStack/db/blob/main/packages/db/src/query/predicate-utils.ts#L21)
13+
14+
Check if one where clause is a logical subset of another.
15+
Returns true if the subset predicate is more restrictive than (or equal to) the superset predicate.
16+
17+
## Parameters
18+
19+
### subset
20+
21+
The potentially more restrictive predicate
22+
23+
[`BasicExpression`](../../@tanstack/namespaces/IR/type-aliases/BasicExpression.md)\<`boolean`\> | `undefined`
24+
25+
### superset
26+
27+
The potentially less restrictive predicate
28+
29+
[`BasicExpression`](../../@tanstack/namespaces/IR/type-aliases/BasicExpression.md)\<`boolean`\> | `undefined`
30+
31+
## Returns
32+
33+
`boolean`
34+
35+
true if subset logically implies superset
36+
37+
## Examples
38+
39+
```ts
40+
// age > 20 is subset of age > 10 (more restrictive)
41+
isWhereSubset(gt(ref('age'), val(20)), gt(ref('age'), val(10))) // true
42+
```
43+
44+
```ts
45+
// age > 10 AND name = 'X' is subset of age > 10 (more conditions)
46+
isWhereSubset(and(gt(ref('age'), val(10)), eq(ref('name'), val('X'))), gt(ref('age'), val(10))) // true
47+
```
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
---
2+
id: minusWherePredicates
3+
title: minusWherePredicates
4+
---
5+
6+
# Function: minusWherePredicates()
7+
8+
```ts
9+
function minusWherePredicates(fromPredicate, subtractPredicate):
10+
| BasicExpression<boolean>
11+
| null;
12+
```
13+
14+
Defined in: [packages/db/src/query/predicate-utils.ts:338](https://github.com/TanStack/db/blob/main/packages/db/src/query/predicate-utils.ts#L338)
15+
16+
Compute the difference between two where predicates: `fromPredicate AND NOT(subtractPredicate)`.
17+
Returns the simplified predicate, or null if the difference cannot be simplified
18+
(in which case the caller should fetch the full fromPredicate).
19+
20+
## Parameters
21+
22+
### fromPredicate
23+
24+
The predicate to subtract from
25+
26+
[`BasicExpression`](../../@tanstack/namespaces/IR/type-aliases/BasicExpression.md)\<`boolean`\> | `undefined`
27+
28+
### subtractPredicate
29+
30+
The predicate to subtract
31+
32+
[`BasicExpression`](../../@tanstack/namespaces/IR/type-aliases/BasicExpression.md)\<`boolean`\> | `undefined`
33+
34+
## Returns
35+
36+
\| [`BasicExpression`](../../@tanstack/namespaces/IR/type-aliases/BasicExpression.md)\<`boolean`\>
37+
\| `null`
38+
39+
The simplified difference, or null if cannot be simplified
40+
41+
## Examples
42+
43+
```ts
44+
// Range difference
45+
minusWherePredicates(
46+
gt(ref('age'), val(10)), // age > 10
47+
gt(ref('age'), val(20)) // age > 20
48+
) // → age > 10 AND age <= 20
49+
```
50+
51+
```ts
52+
// Set difference
53+
minusWherePredicates(
54+
inOp(ref('status'), ['A', 'B', 'C', 'D']), // status IN ['A','B','C','D']
55+
inOp(ref('status'), ['B', 'C']) // status IN ['B','C']
56+
) // → status IN ['A', 'D']
57+
```
58+
59+
```ts
60+
// Common conditions
61+
minusWherePredicates(
62+
and(gt(ref('age'), val(10)), eq(ref('status'), val('active'))), // age > 10 AND status = 'active'
63+
and(gt(ref('age'), val(20)), eq(ref('status'), val('active'))) // age > 20 AND status = 'active'
64+
) // → age > 10 AND age <= 20 AND status = 'active'
65+
```
66+
67+
```ts
68+
// Complete overlap - empty result
69+
minusWherePredicates(
70+
gt(ref('age'), val(20)), // age > 20
71+
gt(ref('age'), val(10)) // age > 10
72+
) // → {type: 'val', value: false} (empty set)
73+
```

0 commit comments

Comments
 (0)