Skip to content

Commit eae0ffc

Browse files
committed
Unify handling of IN and OR
1 parent 2a0f3ee commit eae0ffc

File tree

2 files changed

+87
-1
lines changed

2 files changed

+87
-1
lines changed

packages/db/src/query/predicate-utils.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type { BasicExpression, Func, OrderBy, PropRef } from "./ir.js"
1+
import { Func, Value } from "./ir.js"
2+
import type { BasicExpression, OrderBy, PropRef } from "./ir.js"
23
import type { LoadSubsetOptions } from "../types.js"
34

45
/**
@@ -42,6 +43,25 @@ export function isWhereSubset(
4243
return isWhereSubsetInternal(subset!, superset!)
4344
}
4445

46+
function makeDisjunction(
47+
preds: Array<BasicExpression<boolean>>
48+
): BasicExpression<boolean> {
49+
if (preds.length === 0) {
50+
return new Value(false)
51+
}
52+
if (preds.length === 1) {
53+
return preds[0]!
54+
}
55+
return new Func(`or`, preds)
56+
}
57+
58+
function convertInToOr(inField: InField) {
59+
const equalities = inField.values.map(
60+
(value) => new Func(`eq`, [inField.ref, new Value(value)])
61+
)
62+
return makeDisjunction(equalities)
63+
}
64+
4565
function isWhereSubsetInternal(
4666
subset: BasicExpression<boolean>,
4767
superset: BasicExpression<boolean>
@@ -68,6 +88,22 @@ function isWhereSubsetInternal(
6888
)
6989
}
7090

91+
// Turn x IN [A, B, C] into x = A OR x = B OR x = C
92+
// for unified handling of IN and OR
93+
if (subset.type === `func` && subset.name === `in`) {
94+
const inField = extractInField(subset)
95+
if (inField) {
96+
return isWhereSubsetInternal(convertInToOr(inField), superset)
97+
}
98+
}
99+
100+
if (superset.type === `func` && superset.name === `in`) {
101+
const inField = extractInField(superset)
102+
if (inField) {
103+
return isWhereSubsetInternal(subset, convertInToOr(inField))
104+
}
105+
}
106+
71107
// Handle OR in subset: (A OR B) is subset of C only if both A and B are subsets of C
72108
if (subset.type === `func` && subset.name === `or`) {
73109
return subset.args.every((arg) =>
@@ -106,6 +142,7 @@ function isWhereSubsetInternal(
106142
)
107143
}
108144

145+
/*
109146
// Handle eq vs in
110147
if (subsetFunc.name === `eq` && supersetFunc.name === `in`) {
111148
const subsetFieldEq = extractEqualityField(subsetFunc)
@@ -147,6 +184,7 @@ function isWhereSubsetInternal(
147184
)
148185
}
149186
}
187+
*/
150188
}
151189

152190
// Conservative: if we can't determine, return false

packages/db/tests/predicate-utils.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,54 @@ describe(`isWhereSubset`, () => {
199199
isWhereSubset(inOp(ref(`age`), [5, 20]), inOp(ref(`age`), [5, 10, 15]))
200200
).toBe(false)
201201
})
202+
203+
it(`should handle empty IN array: age IN [] is subset of age IN []`, () => {
204+
expect(isWhereSubset(inOp(ref(`age`), []), inOp(ref(`age`), []))).toBe(
205+
true
206+
)
207+
})
208+
209+
it(`should handle empty IN array: age IN [] is subset of age IN [5, 10]`, () => {
210+
expect(
211+
isWhereSubset(inOp(ref(`age`), []), inOp(ref(`age`), [5, 10]))
212+
).toBe(true)
213+
})
214+
215+
it(`should handle empty IN array: age IN [5, 10] is NOT subset of age IN []`, () => {
216+
expect(
217+
isWhereSubset(inOp(ref(`age`), [5, 10]), inOp(ref(`age`), []))
218+
).toBe(false)
219+
})
220+
221+
it(`should handle singleton IN array: age = 5 is subset of age IN [5]`, () => {
222+
expect(isWhereSubset(eq(ref(`age`), val(5)), inOp(ref(`age`), [5]))).toBe(
223+
true
224+
)
225+
})
226+
227+
it(`should handle singleton IN array: age = 10 is NOT subset of age IN [5]`, () => {
228+
expect(
229+
isWhereSubset(eq(ref(`age`), val(10)), inOp(ref(`age`), [5]))
230+
).toBe(false)
231+
})
232+
233+
it(`should handle singleton IN array: age IN [5] is subset of age IN [5, 10, 15]`, () => {
234+
expect(
235+
isWhereSubset(inOp(ref(`age`), [5]), inOp(ref(`age`), [5, 10, 15]))
236+
).toBe(true)
237+
})
238+
239+
it(`should handle singleton IN array: age IN [20] is NOT subset of age IN [5, 10, 15]`, () => {
240+
expect(
241+
isWhereSubset(inOp(ref(`age`), [20]), inOp(ref(`age`), [5, 10, 15]))
242+
).toBe(false)
243+
})
244+
245+
it(`should handle singleton IN array: age IN [5, 10, 15] is NOT subset of age IN [5]`, () => {
246+
expect(
247+
isWhereSubset(inOp(ref(`age`), [5, 10, 15]), inOp(ref(`age`), [5]))
248+
).toBe(false)
249+
})
202250
})
203251

204252
describe(`AND combinations`, () => {

0 commit comments

Comments
 (0)