Skip to content
Merged
5 changes: 5 additions & 0 deletions packages/orm/src/client/crud-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,11 @@ type CommonPrimitiveFilter<
*/
gte?: DataType;

/**
* Checks if the value is between the specified values (inclusive).
*/
between?: [start: DataType, end: DataType];

/**
* Builds a negated filter.
*/
Expand Down
5 changes: 5 additions & 0 deletions packages/orm/src/client/crud/dialects/base-dialect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,11 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
.with('lte', () => this.eb(lhs, '<=', rhs))
.with('gt', () => this.eb(lhs, '>', rhs))
.with('gte', () => this.eb(lhs, '>=', rhs))
.with('between', () => {
invariant(Array.isArray(rhs), 'right hand side must be an array');
const [start, end] = rhs;
return this.eb.and([this.eb(lhs, '>=', start), this.eb(lhs, '<=', end)]);
})
.with('not', () => this.eb.not(recurse(value)))
// aggregations
.with(P.union(...AGGREGATE_OPERATORS), (op) => {
Expand Down
1 change: 1 addition & 0 deletions packages/orm/src/client/crud/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,7 @@ export class InputValidator<Schema extends SchemaDef> {
lte: baseSchema.optional(),
gt: baseSchema.optional(),
gte: baseSchema.optional(),
between: baseSchema.array().optional(),
not: makeThis().optional(),
...(withAggregations?.includes('_count')
? { _count: this.makeNumberFilterSchema(z.number().int(), false, false).optional() }
Expand Down
1 change: 1 addition & 0 deletions packages/server/src/api/rest/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ const FilterOperations = [
'lte',
'gt',
'gte',
'between',
'contains',
'icontains',
'search',
Expand Down
8 changes: 8 additions & 0 deletions packages/server/test/api/rest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,14 @@ describe('REST server tests', () => {
expect(r.body.data).toHaveLength(1);
expect(r.body.data[0]).toMatchObject({ id: 1 });

r = await handler({
method: 'get',
path: '/post',
query: { ['filter[viewCount$between]']: [[1, 2]] },
client,
});
expect(r.body.data).toHaveLength(1);

// Boolean filter
r = await handler({
method: 'get',
Expand Down
92 changes: 92 additions & 0 deletions tests/e2e/orm/client-api/filter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,58 @@ describe('Client filter tests ', () => {
}),
).toResolveWithLength(2);

// between
await expect(
client.user.findMany({
where: { email: { between: ['a@test.com', 'a@test.com'] } },
}),
).toResolveWithLength(0);
await expect(
client.user.findMany({
where: { email: { between: ['a@test.com', 'b@test.com'] } },
}),
).toResolveWithLength(0);
await expect(
client.user.findMany({
where: { email: { between: ['z@test.com', 'a@test.com'] } },
}),
).toResolveWithLength(0);
await expect(
client.user.findMany({
where: { email: { between: ['u1@test.com', 'u1@test.com'] } },
}),
).toResolveWithLength(1);
await expect(
client.user.findMany({
where: { email: { between: ['u2@test.com', 'u2@test.com'] } },
}),
).toResolveWithLength(1);
await expect(
client.user.findMany({
where: { email: { between: ['u1@test.com', 'u2@test.com'] } },
}),
).toResolveWithLength(2);
await expect(
client.user.findMany({
where: { email: { between: ['u2@test.com', 'u3%@test.com'] } },
}),
).toResolveWithLength(2);
await expect(
client.user.findMany({
where: { email: { between: ['a@test.com', 'u3%@test.com'] } },
}),
).toResolveWithLength(3);
await expect(
client.user.findMany({
where: { email: { between: ['a@test.com', 'z@test.com'] } },
}),
).toResolveWithLength(3);
await expect(
client.user.findMany({
where: { email: { between: ['u1@test.com', 'u3%@test.com'] } },
}),
).toResolveWithLength(3);

// contains
await expect(
client.user.findFirst({
Expand Down Expand Up @@ -409,6 +461,13 @@ describe('Client filter tests ', () => {
await expect(client.profile.findMany({ where: { age: { gte: 20 } } })).toResolveWithLength(1);
await expect(client.profile.findMany({ where: { age: { gte: 21 } } })).toResolveWithLength(0);

// between
await expect(client.profile.findMany({ where: { age: { between: [20, 20] } } })).toResolveWithLength(1);
await expect(client.profile.findMany({ where: { age: { between: [19, 20] } } })).toResolveWithLength(1);
await expect(client.profile.findMany({ where: { age: { between: [20, 21] } } })).toResolveWithLength(1);
await expect(client.profile.findMany({ where: { age: { between: [19, 19] } } })).toResolveWithLength(0);
await expect(client.profile.findMany({ where: { age: { between: [21, 21] } } })).toResolveWithLength(0);

// not
await expect(
client.profile.findFirst({
Expand Down Expand Up @@ -577,6 +636,39 @@ describe('Client filter tests ', () => {
}),
).resolves.toMatchObject(user2);

// between
await expect(
client.user.findMany({
where: { createdAt: { between: [user1.createdAt, user1.createdAt] } },
}),
).toResolveWithLength(1);
await expect(
client.user.findMany({
where: { createdAt: { between: [user2.createdAt, user2.createdAt] } },
}),
).toResolveWithLength(1);
await expect(
client.user.findMany({
where: { createdAt: { between: [user1.createdAt, user2.createdAt] } },
}),
).toResolveWithLength(2);
await expect(
client.user.findMany({
where: { createdAt: { between: [new Date('2020-01-01'), new Date('2020-01-02')] } },
}),
).toResolveWithLength(0);
await expect(
client.user.findMany({
where: { createdAt: { between: [new Date('2020-01-01'), user1.createdAt] } },
}),
).toResolveWithLength(1);

await expect(
client.user.findMany({
where: { createdAt: { between: [new Date('2020-01-01'), user2.createdAt] } },
}),
).toResolveWithLength(2);

// not
await expect(
client.user.findFirst({
Expand Down
Loading