diff --git a/packages/orm/src/client/executor/zenstack-query-executor.ts b/packages/orm/src/client/executor/zenstack-query-executor.ts index c7d9cbadc..a2b418029 100644 --- a/packages/orm/src/client/executor/zenstack-query-executor.ts +++ b/packages/orm/src/client/executor/zenstack-query-executor.ts @@ -460,6 +460,13 @@ export class ZenStackQueryExecutor extends DefaultQueryExecutor { ? this.getInsertIds(mutationInfo.model, queryNode, queryResult) : preUpdateIds; + if (mutatedIds && mutatedIds.length === 0) { + // zero rows matched (e.g. a policy-denied or genuinely empty + // updateMany/deleteMany) — nothing to reload, and NOT the "can't + // determine ids at all" case below, so don't warn. + return []; + } + if (mutatedIds) { const idFields = requireIdFields(this.client.$schema, mutationInfo.model); const eb = expressionBuilder(); diff --git a/tests/regression/test/issue-2884.test.ts b/tests/regression/test/issue-2884.test.ts new file mode 100644 index 000000000..91d43785d --- /dev/null +++ b/tests/regression/test/issue-2884.test.ts @@ -0,0 +1,29 @@ +import { createTestClient } from '@zenstackhq/testtools'; +import { describe, expect, it } from 'vitest'; + +describe('Regression for issue 2884', () => { + it('zero-match updateMany on MySQL does not throw when an afterEntityMutation hook is registered', async () => { + const db = await createTestClient( + ` +model User { + id Int @id @default(autoincrement()) + email String @unique +} + `, + { provider: 'mysql', usePrismaPush: true }, + ); + + const hooked = db.$use({ + id: 'noop', + onEntityMutation: { + afterEntityMutation: async () => {}, + }, + }); + + // no row with id 999 exists -> preUpdateIds resolves to [] -> should be a + // normal zero-match update, not a thrown SQL syntax error + await expect( + hooked.user.updateMany({ where: { id: 999 }, data: { email: 'x@example.com' } }), + ).resolves.toEqual({ count: 0 }); + }); +});