tests.ts•4.27 kB
import { Providers, RelationModes } from '../_utils/providers'
import testMatrix from './_matrix'
// @ts-ignore
import type { PrismaClient } from './generated/prisma/client'
declare let prisma: PrismaClient
/**
* Regression test for issue #8612
* Optimistic concurrency control (OCC)
*/
testMatrix.setupTestSuite(({ provider, relationMode }) => {
beforeEach(async () => {
await prisma.resource.create({ data: {} })
})
afterEach(async () => {
await prisma.resource.deleteMany()
})
// TODO optimistic concurrency control is not working with relationMode=prisma
// See https://github.com/prisma/prisma/issues/21867
skipTestIf(relationMode === RelationModes.PRISMA)('updateMany', async () => {
const fn = async () => {
// we get our concurrent resource at some point in time
const resource = (await prisma.resource.findFirst())!
// at this stage, the occStamp is always equal to `0`
expect(resource).toMatchObject({ occStamp: 0 })
// for this resource, we tell the database to update it
await prisma.resource.updateMany({
// TODO: add count
where: { occStamp: resource.occStamp },
data: { occStamp: { increment: 1 } },
})
// if the occStamp changed between the findFirst and the
// updateMany, the updateMany will not update anything
// 🛑 but the query engine ignores that at the moment
}
// so we want to fire 5 requests in parallel that are OCCed
await Promise.allSettled([fn(), fn(), fn(), fn(), fn()])
// if OCC worked correctly, then the occStamp should be `1`
// because our where clause specified to ONLY update if `0`
// this shows that the update query engine query is not atomic
expect(await prisma.resource.findFirst()).toMatchObject({ occStamp: 1 })
})
// TODO optimistic concurrency control is not working with relationMode=prisma
// See https://github.com/prisma/prisma/issues/21867
skipTestIf(relationMode === RelationModes.PRISMA)('update', async () => {
const fn = async () => {
const resource = (await prisma.resource.findFirst())!
expect(resource).toMatchObject({ occStamp: 0 })
await prisma.resource.update({
where: { occStamp: resource.occStamp },
data: { occStamp: { increment: 1 } },
})
}
await Promise.allSettled([fn(), fn(), fn(), fn(), fn()])
expect(await prisma.resource.findFirst()).toMatchObject({ occStamp: 1 })
})
// skipping these providers because they all emit a transaction conflict error
testIf(!['mongodb', 'cockroachdb', 'sqlite'].includes(provider))('deleteMany', async () => {
const fn = async (): Promise<number> => {
const result = await prisma.resource.deleteMany({
where: { occStamp: 0 },
})
return result.count
}
const results = await Promise.all([fn(), fn(), fn(), fn(), fn()])
const totalCount = results.reduce((acc, result) => acc + result, 0)
expect(totalCount).toBe(1)
})
// issue with mysql: https://github.com/prisma/prisma/issues/15470
testIf(provider !== Providers.MYSQL)('upsert', async () => {
const fn = async () => {
const resource = (await prisma.resource.findFirst())!
expect(resource).toMatchObject({ occStamp: 0 })
await prisma.resource.upsert({
where: { occStamp: resource.occStamp },
update: { occStamp: { increment: 1 } },
create: {},
})
}
await Promise.allSettled([fn(), fn(), fn(), fn(), fn()])
expect(await prisma.resource.findFirst()).toMatchObject({ occStamp: 1 })
})
test('update with upsert relation', async () => {
const fn = async () => {
const resource = (await prisma.resource.findFirst())!
expect(resource).toMatchObject({ occStamp: 0 })
await prisma.resource.update({
where: { occStamp: resource.occStamp },
data: {
occStamp: { increment: 1 },
child: {
upsert: {
create: {},
update: {},
},
},
},
})
}
await Promise.allSettled([fn(), fn(), fn(), fn(), fn()])
expect(await prisma.resource.findFirst()).toMatchObject({ occStamp: 1 })
expect(await prisma.child.count()).toBe(1)
})
})