diff --git a/packages/orm/src/client/crud/dialects/base-dialect.ts b/packages/orm/src/client/crud/dialects/base-dialect.ts index 5fae51ef5..7a75acf44 100644 --- a/packages/orm/src/client/crud/dialects/base-dialect.ts +++ b/packages/orm/src/client/crud/dialects/base-dialect.ts @@ -264,9 +264,12 @@ export abstract class BaseCrudDialect { .with('AND', () => this.and(...enumerate(payload).map((subPayload) => this.buildFilter(model, modelAlias, subPayload))), ) - .with('OR', () => - this.or(...enumerate(payload).map((subPayload) => this.buildFilter(model, modelAlias, subPayload))), - ) + .with('OR', () => { + const branches = enumerate(payload) + .filter((subPayload) => !this.isAllUndefinedFilter(subPayload)) + .map((subPayload) => this.buildFilter(model, modelAlias, subPayload)); + return this.or(...branches); + }) .with('NOT', () => this.eb.not(this.buildCompositeFilter(model, modelAlias, 'AND', payload))) .exhaustive(); } @@ -800,6 +803,9 @@ export abstract class BaseCrudDialect { if (excludeKeys.includes(op)) { continue; } + if (value === undefined) { + continue; + } const rhs = Array.isArray(value) ? value.map(getRhs) : getRhs(value); const condition = match(op) .with('equals', () => (rhs === null ? this.eb(lhs, 'is', null) : this.eb(lhs, '=', rhs))) @@ -881,6 +887,10 @@ export abstract class BaseCrudDialect { continue; } + if (value === undefined) { + continue; + } + invariant(typeof value === 'string', `${key} value must be a string`); const escapedValue = this.escapeLikePattern(value); @@ -1162,7 +1172,8 @@ export abstract class BaseCrudDialect { // client-level: check both uncapitalized (current) and original (backward compat) model name const uncapModel = lowerCaseFirst(model); - const omitConfig = (this.options.omit as Record | undefined)?.[uncapModel] ?? + const omitConfig = + (this.options.omit as Record | undefined)?.[uncapModel] ?? (this.options.omit as Record | undefined)?.[model]; if (omitConfig && typeof omitConfig === 'object' && typeof omitConfig[field] === 'boolean') { return omitConfig[field]; @@ -1299,6 +1310,14 @@ export abstract class BaseCrudDialect { return this.eb.lit(this.transformInput(false, 'Boolean', false) as boolean); } + private isAllUndefinedFilter(payload: unknown): boolean { + if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { + return false; + } + const entries = Object.entries(payload); + return entries.length > 0 && entries.every(([, v]) => v === undefined); + } + public isTrue(expression: Expression) { const node = expression.toOperationNode(); if (node.kind !== 'ValueNode') { @@ -1357,7 +1376,9 @@ export abstract class BaseCrudDialect { const computedFields = this.options.computedFields as Record; // check both uncapitalized (current) and original (backward compat) model name const computedModel = fieldDef.originModel ?? model; - computer = computedFields?.[lowerCaseFirst(computedModel)]?.[field] ?? computedFields?.[computedModel]?.[field]; + computer = + computedFields?.[lowerCaseFirst(computedModel)]?.[field] ?? + computedFields?.[computedModel]?.[field]; } if (!computer) { throw createConfigError(`Computed field "${field}" implementation not provided for model "${model}"`); diff --git a/tests/e2e/orm/client-api/filter.test.ts b/tests/e2e/orm/client-api/filter.test.ts index 36173ca2d..ff9574a41 100644 --- a/tests/e2e/orm/client-api/filter.test.ts +++ b/tests/e2e/orm/client-api/filter.test.ts @@ -801,5 +801,57 @@ describe('Client filter tests ', () => { await expect(client.user.findMany({ where: { id: undefined } })).toResolveWithLength(1); }); + it('ignores undefined branch inside OR filters', async () => { + await createUser('u1@test.com', { + name: 'First', + role: 'ADMIN', + profile: { create: { id: 'p1', bio: 'bio1' } }, + }); + const user2 = await createUser('u2@test.com', { + name: 'Second', + role: 'USER', + profile: { create: { id: 'p2', bio: 'bio2' } }, + }); + + const baseline = await client.user.findFirst({ + where: { + OR: [{ id: user2.id }], + } as any, + orderBy: { createdAt: 'asc' }, + }); + + const withUndefinedBranch = await client.user.findFirst({ + where: { + OR: [{ id: undefined }, { id: user2.id }], + } as any, + orderBy: { createdAt: 'asc' }, + }); + + const onlyUndefinedBranch = await client.user.findFirst({ + where: { + OR: [{ id: undefined }], + } as any, + orderBy: { createdAt: 'asc' }, + }); + + expect(baseline?.email).toBe(user2.email); + expect(withUndefinedBranch?.email).toBe(baseline?.email); + expect(onlyUndefinedBranch).toBeNull(); + }); + + it('strips undefined filter operators inside OR branches', async () => { + await createUser('alice@test.com', { name: 'Alice', role: 'ADMIN' }); + await createUser('bob@test.com', { name: 'Bob', role: 'USER' }); + + const result = await client.user.findMany({ + where: { + OR: [{ name: { startsWith: 'A', contains: undefined } }], + } as any, + }); + + expect(result).toHaveLength(1); + expect(result[0]!.name).toBe('Alice'); + }); + // TODO: filter for bigint, decimal, bytes });