From 5bf2a71c9e9ab88cd74c5437163cc73967dcaeb1 Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Thu, 15 Oct 2026 12:09:11 +0100 Subject: [PATCH] fix(prisma-adapter): route insensitive id increments through the lookup path --- .../prisma-adapter/src/prisma-adapter.test.ts | 35 +++++++++++++++++++ packages/prisma-adapter/src/prisma-adapter.ts | 9 ++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/packages/prisma-adapter/src/prisma-adapter.test.ts b/packages/prisma-adapter/src/prisma-adapter.test.ts index fcd0944a38..cadbd57b4f 100644 --- a/packages/prisma-adapter/src/prisma-adapter.test.ts +++ b/packages/prisma-adapter/src/prisma-adapter.test.ts @@ -607,6 +607,41 @@ describe("prisma-adapter", () => { }); }); + it("incrementOne resolves an insensitive id before the keyed update", async () => { + const update = vi.fn().mockResolvedValue({ id: "row-1", remaining: 4 }); + const txClient = { + verification: { + findFirst: vi.fn().mockResolvedValue({ id: "row-1" }), + update, + }, + }; + const adapter = prismaAdapter( + { + $transaction: vi.fn(async (cb) => cb(txClient)), + verification: {}, + } as never, + { provider: "postgresql" }, + )({ + verification: { + additionalFields: { remaining: { type: "number" } }, + }, + } as BetterAuthOptions); + + await adapter.incrementOne({ + model: "verification", + where: [{ field: "id", value: "row-1", mode: "insensitive" }], + increment: { remaining: -1 }, + }); + + expect(update).toHaveBeenCalledWith({ + where: { + id: "row-1", + AND: [{ id: { equals: "row-1", mode: "insensitive" } }], + }, + data: { remaining: { increment: -1 } }, + }); + }); + it("update keeps an insensitive predicate next to a unique key", async () => { const update = vi.fn().mockResolvedValue({ id: "user-id" }); const adapter = prismaAdapter( diff --git a/packages/prisma-adapter/src/prisma-adapter.ts b/packages/prisma-adapter/src/prisma-adapter.ts index 8acf194212..96651b1320 100644 --- a/packages/prisma-adapter/src/prisma-adapter.ts +++ b/packages/prisma-adapter/src/prisma-adapter.ts @@ -746,7 +746,14 @@ export const prismaAdapter = (prisma: PrismaClient, config: PrismaConfig) => { // transaction and update by id. Either way the original guard stays in // the where, so a racer that invalidated it (e.g. remaining dropped to // 0) yields P2025 and we report no mutation. - const hasIdField = where?.some((w) => w.field === "id"); + // Only a case-sensitive AND eq on id can stay a flat key for update(). + const hasIdField = where?.some( + (w) => + w.field === "id" && + (w.operator === "eq" || !w.operator) && + w.connector !== "OR" && + !isInsensitiveForPrisma(w), + ); if (hasIdField) { const whereClause = convertWhereClause({ model: modelKey,