Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions packages/orm/src/client/zod/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,13 +359,21 @@ export class ZodSchemaFactory<
ZodUtils.addDecimalValidation(z.string(), attributes, this.extraValidationsEnabled),
]);
})
.with('DateTime', () => this.makeDateTimeValueSchema())
.with('DateTime', () =>
this.hasAttribute(attributes, '@db.Date')
? z.union([z.iso.date(), this.makeDateTimeValueSchema()])
: this.makeDateTimeValueSchema(),
)
.with('Bytes', () => z.instanceof(Uint8Array))
.with('Json', () => this.makeJsonValueSchema())
.otherwise(() => z.unknown());
}
}

private hasAttribute(attributes: readonly AttributeApplication[] | undefined, name: string) {
return attributes?.some((attribute) => attribute.name === name) ?? false;
}

@cache()
private makeEnumSchema(_enum: string) {
const enumDef = getEnum(this.schema, _enum);
Expand Down Expand Up @@ -863,8 +871,9 @@ export class ZodSchemaFactory<
withAggregations: boolean,
allowedFilterKinds: string[] | undefined,
): ZodType {
const filterValueSchema = z.union([z.iso.date(), this.makeDateTimeValueSchema()]);
const schema = this.makeCommonPrimitiveFilterSchema(
this.makeDateTimeValueSchema(),
filterValueSchema,
optional,
() => z.lazy(() => this.makeDateTimeFilterSchema(optional, withAggregations, allowedFilterKinds)),
withAggregations ? ['_count', '_min', '_max'] : undefined,
Expand Down
124 changes: 124 additions & 0 deletions tests/e2e/orm/client-api/db-date-filter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { createTestClient } from '@zenstackhq/testtools';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';

// This test verifies that plain date strings are accepted for DateTime fields.
describe('plain date string filter tests for DateTime fields', () => {
const schema = `
model Event {
id Int @id @default(autoincrement())
name String
eventDate DateTime @db.Date
createdAt DateTime @default(now())
}
`;

let client: any;

beforeEach(async () => {
client = await createTestClient(schema, {
usePrismaPush: true,
provider: 'postgresql',
});
});

afterEach(async () => {
await client?.$disconnect();
});

it('accepts a plain date string ("YYYY-MM-DD") in an equality filter on a @db.Date field', async () => {
const filterSchema = client.$zod.makeFindManySchema('Event');
const result = filterSchema.safeParse({ where: { eventDate: '2007-05-23' } });
expect(result.success, `Expected plain date string to be accepted, got: ${JSON.stringify(result.error)}`).toBe(
true,
);
});

it('accepts an ISO datetime string in a filter on a @db.Date field', async () => {
const filterSchema = client.$zod.makeFindManySchema('Event');
const result = filterSchema.safeParse({ where: { eventDate: '2007-05-23T00:00:00.000Z' } });
expect(result.success).toBe(true);
});

it('accepts a Date object in a filter on a @db.Date field', async () => {
const filterSchema = client.$zod.makeFindManySchema('Event');
const result = filterSchema.safeParse({ where: { eventDate: new Date('2007-05-23') } });
expect(result.success).toBe(true);
});

it('rejects an invalid date string in a filter on a @db.Date field', async () => {
const filterSchema = client.$zod.makeFindManySchema('Event');
const result = filterSchema.safeParse({ where: { eventDate: 'not-a-date' } });
expect(result.success).toBe(false);
});

it('filters records correctly using a plain date string', async () => {
await client.event.create({ data: { name: 'Conference', eventDate: new Date('2007-05-23') } });
await client.event.create({ data: { name: 'Workshop', eventDate: new Date('2024-01-15') } });

const found = await client.event.findMany({ where: { eventDate: '2007-05-23' } });
expect(found).toHaveLength(1);
expect(found[0].name).toBe('Conference');
});

it('supports gt/lt filters with plain date strings on a @db.Date field', async () => {
await client.event.create({ data: { name: 'Past', eventDate: new Date('2020-01-01') } });
await client.event.create({ data: { name: 'Middle', eventDate: new Date('2024-01-01') } });
await client.event.create({ data: { name: 'Future', eventDate: new Date('2030-01-01') } });

const filterSchema = client.$zod.makeFindManySchema('Event');
const result = filterSchema.safeParse({ where: { eventDate: { gt: '2025-01-01' } } });
expect(result.success, `Expected gt filter to be accepted, got: ${JSON.stringify(result.error)}`).toBe(true);

const lessThanResult = filterSchema.safeParse({ where: { eventDate: { lt: '2025-01-01' } } });
expect(
lessThanResult.success,
`Expected lt filter to be accepted, got: ${JSON.stringify(lessThanResult.error)}`,
).toBe(true);

const found = await client.event.findMany({ where: { eventDate: { gt: '2025-01-01' } } });
expect(found).toHaveLength(1);
expect(found[0].name).toBe('Future');

const lessThanFound = await client.event.findMany({ where: { eventDate: { lt: '2025-01-01' } } });
expect(lessThanFound).toHaveLength(2);
expect(lessThanFound.map((item: { name: string }) => item.name).sort()).toEqual(['Middle', 'Past']);
});

it('accepts plain date strings in create and update payloads for a @db.Date field only', async () => {
const createSchema = client.$zod.makeCreateSchema('Event');
const createResult = createSchema.safeParse({ data: { name: 'Conference', eventDate: '2007-05-23' } });
expect(
createResult.success,
`Expected create payload for @db.Date field to be accepted, got: ${JSON.stringify(createResult.error)}`,
).toBe(true);

const invalidCreateResult = createSchema.safeParse({ data: { name: 'Conference', createdAt: '2007-05-23' } });
expect(invalidCreateResult.success).toBe(false);

const created = await client.event.create({ data: { name: 'Conference', eventDate: '2007-05-23' } });

const updateSchema = client.$zod.makeUpdateSchema('Event');
const updateResult = updateSchema.safeParse({ where: { id: created.id }, data: { eventDate: '2008-05-23' } });
expect(
updateResult.success,
`Expected update payload for @db.Date field to be accepted, got: ${JSON.stringify(updateResult.error)}`,
).toBe(true);

const invalidUpdateResult = updateSchema.safeParse({
where: { id: created.id },
data: { createdAt: '2008-05-23' },
});
expect(invalidUpdateResult.success).toBe(false);

await client.event.update({ where: { id: created.id }, data: { eventDate: '2008-05-23' } });

const updated = await client.event.findMany({ where: { id: created.id, eventDate: '2008-05-23' } });
expect(updated).toHaveLength(1);
});

it('plain date string is rejected on a regular DateTime field (no @db.Date)', async () => {
const filterSchema = client.$zod.makeFindManySchema('Event');
const result = filterSchema.safeParse({ where: { createdAt: '2007-05-23' } });
expect(result.success).toBe(false);
});
});
Loading