Skip to main content
Glama

Prisma MCP Server

Official
by prisma
Apache 2.0
4
44,213
  • Linux
  • Apple
tests.ts26.5 kB
import { ClientEngineType } from '@prisma/internals' import { copycat } from '@snaplet/copycat' import { AdapterProviders, Providers } from '../_utils/providers' import { NewPrismaClient } from '../_utils/types' import testMatrix from './_matrix' // @ts-ignore import type { Prisma as PrismaNamespace, PrismaClient } from './generated/prisma/client' declare let prisma: PrismaClient declare let Prisma: typeof PrismaNamespace declare const newPrismaClient: NewPrismaClient<PrismaClient, typeof PrismaClient> const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) testMatrix.setupTestSuite( ({ provider, engineType, driverAdapter }, _suiteMeta, clientMeta) => { // TODO: Technically, only "high concurrency" test requires larger timeout // but `jest.setTimeout` does not work inside of the test at the moment // https://github.com/facebook/jest/issues/11543 jest.setTimeout(60_000) beforeEach(async () => { await prisma.user.deleteMany() }) // Regression test for https://github.com/prisma/prisma/issues/19137. test('issue #19137', async () => { expect.assertions(1) await prisma .$transaction( // @ts-expect-error: Type 'void' is not assignable to type 'Promise<unknown>' /* note how there's no `async` here */ (tx) => { console.log('1') console.log(tx) console.log('2') }, ) .then(() => expect(true).toBe(true)) }, 30_000) /** * Minimal example of an interactive transaction */ test('basic', async () => { const result = await prisma.$transaction(async (prisma) => { await prisma.user.create({ data: { email: 'user_1@website.com', }, }) await prisma.user.create({ data: { email: 'user_2@website.com', }, }) return prisma.user.findMany() }) expect(result.length).toBe(2) }) /** * Transactions should fail after the default timeout */ test('timeout default', async () => { const result = prisma.$transaction(async (prisma) => { await prisma.user.create({ data: { email: 'user_1@website.com', }, }) await delay(6000) }) await expect(result).rejects.toMatchObject({ message: expect.stringMatching( /A commit cannot be executed on an expired transaction. The timeout for this transaction was 5000 ms, however \d+ ms passed since the start of the transaction. Consider increasing the interactive transaction timeout or doing less work in the transaction./, ), code: 'P2028', clientVersion: '0.0.0', }) expect(await prisma.user.findMany()).toHaveLength(0) }) /** * Transactions should fail if they time out on `timeout` */ test('timeout override', async () => { const result = prisma.$transaction( async (prisma) => { await prisma.user.create({ data: { email: 'user_1@website.com', }, }) await delay(600) }, { maxWait: 200, timeout: 500, }, ) await expect(result).rejects.toMatchObject({ message: expect.stringMatching( /A commit cannot be executed on an expired transaction. The timeout for this transaction was 500 ms, however \d+ ms passed since the start of the transaction. Consider increasing the interactive transaction timeout or doing less work in the transaction./, ), }) expect(await prisma.user.findMany()).toHaveLength(0) }) /** * Transactions should fail if they time out on `timeout` by PrismaClient */ test('timeout override by PrismaClient', async () => { const isolatedPrisma = newPrismaClient({ transactionOptions: { maxWait: 200, timeout: 500, }, }) const result = isolatedPrisma.$transaction(async (prisma) => { await prisma.user.create({ data: { email: 'user_1@website.com', }, }) await delay(600) }) if (driverAdapter === AdapterProviders.JS_NEON) { // Neon sometimes raises 'Unable to start a transaction in the given time.' await expect(result).rejects.toMatchObject({ message: expect.stringMatching(/Transaction API error/), }) } else { await expect(result).rejects.toMatchObject({ message: expect.stringMatching( /A commit cannot be executed on an expired transaction. The timeout for this transaction was 500 ms, however \d+ ms passed since the start of the transaction. Consider increasing the interactive transaction timeout or doing less work in the transaction./, ), }) } expect(await prisma.user.findMany()).toHaveLength(0) }) /** * Transactions should fail and rollback if thrown within */ test('rollback throw', async () => { const result = prisma.$transaction(async (prisma) => { await prisma.user.create({ data: { email: 'user_1@website.com', }, }) throw new Error('you better rollback now') }) await expect(result).rejects.toThrowErrorMatchingInlineSnapshot(`"you better rollback now"`) const users = await prisma.user.findMany() expect(users.length).toBe(0) }) /** * Transactions should fail and rollback if a value is thrown within */ test('rollback throw value', async () => { const result = prisma.$transaction(async (prisma) => { await prisma.user.create({ data: { email: 'user_1@website.com', }, }) throw 'you better rollback now' }) await expect(result).rejects.toBe(`you better rollback now`) const users = await prisma.user.findMany() expect(users.length).toBe(0) }) /** * A transaction might fail if it's called inside another transaction * //! this works only for postgresql */ testIf(provider === Providers.POSTGRESQL)('postgresql: nested create', async () => { const result = prisma.$transaction(async (tx) => { await tx.user.create({ data: { email: 'user_1@website.com', }, }) await prisma.$transaction(async (tx) => { await tx.user.create({ data: { email: 'user_2@website.com', }, }) }) return tx.user.findMany() }) await expect(result).resolves.toHaveLength(2) }) /** * We don't allow certain methods to be called in a transaction */ test('forbidden', async () => { const forbidden = ['$connect', '$disconnect', '$on', '$transaction'] expect.assertions(forbidden.length + 1) const result = prisma.$transaction((prisma) => { for (const method of forbidden) { expect(prisma).not.toHaveProperty(method) } return Promise.resolve() }) await expect(result).resolves.toBe(undefined) }) /** * If one of the query fails, all queries should cancel */ testIf(clientMeta.runtime !== 'edge')('rollback query', async () => { const result = prisma.$transaction(async (prisma) => { await prisma.user.create({ data: { id: copycat.uuid(1).replaceAll('-', '').slice(-24), email: 'user_1@website.com', }, }) await prisma.user.create({ data: { id: copycat.uuid(2).replaceAll('-', '').slice(-24), email: 'user_1@website.com', }, }) }) await expect(result).rejects.toMatchPrismaErrorSnapshot() const users = await prisma.user.findMany() expect(users.length).toBe(0) }) test('already committed', async () => { let transactionBoundPrisma await prisma.$transaction((prisma) => { transactionBoundPrisma = prisma return Promise.resolve() }) const result = prisma.$transaction(async () => { await transactionBoundPrisma.user.create({ data: { email: 'user_1@website.com', }, }) }) await expect(result).rejects.toMatchObject({ message: expect.stringContaining('Transaction API error: Transaction already closed'), code: 'P2028', clientVersion: '0.0.0', }) if (clientMeta.runtime !== 'edge') { await expect(result).rejects.toMatchPrismaErrorInlineSnapshot(` " Invalid \`transactionBoundPrisma.user.create()\` invocation in /client/tests/functional/interactive-transactions/tests.ts:0:0 XX }) XX XX const result = prisma.$transaction(async () => { → XX await transactionBoundPrisma.user.create( Transaction API error: Transaction already closed: A query cannot be executed on a committed transaction." `) } const users = await prisma.user.findMany() expect(users.length).toBe(0) }) /** * Batching should work with using the interactive transaction logic */ test('batching', async () => { await prisma.$transaction([ prisma.user.create({ data: { email: 'user_1@website.com', }, }), prisma.user.create({ data: { email: 'user_2@website.com', }, }), ]) const users = await prisma.user.findMany() expect(users.length).toBe(2) }) /** * A bad batch should rollback using the interactive transaction logic * // TODO: skipped because output differs from binary to library */ testIf(engineType !== ClientEngineType.Binary && clientMeta.runtime !== 'edge')('batching rollback', async () => { const result = prisma.$transaction([ prisma.user.create({ data: { id: copycat.uuid(1).replaceAll('-', '').slice(-24), email: 'user_1@website.com', }, }), prisma.user.create({ data: { id: copycat.uuid(2).replaceAll('-', '').slice(-24), email: 'user_1@website.com', }, }), ]) await expect(result).rejects.toMatchPrismaErrorSnapshot() const users = await prisma.user.findMany() expect(users.length).toBe(0) }) testIf(clientMeta.runtime !== 'edge')('batching rollback within callback', async () => { const result = prisma.$transaction(async (tx) => { await Promise.all([ tx.user.create({ data: { id: copycat.uuid(1).replaceAll('-', '').slice(-24), email: 'user_1@website.com', }, }), tx.user.create({ data: { id: copycat.uuid(2).replaceAll('-', '').slice(-24), email: 'user_2@website.com', }, }), ]) await tx.user.create({ data: { id: copycat.uuid(3).replaceAll('-', '').slice(-24), email: 'user_1@website.com', }, }) }) await expect(result).rejects.toMatchPrismaErrorSnapshot() const users = await prisma.user.findMany() expect(users.length).toBe(0) }) /** * A bad batch should rollback using the interactive transaction logic * // TODO: skipped because output differs from binary to library */ testIf(engineType !== ClientEngineType.Binary && provider !== Providers.MONGODB && clientMeta.runtime !== 'edge')( 'batching raw rollback', async () => { await prisma.user.create({ data: { id: '1', email: 'user_1@website.com', }, }) const result = provider === Providers.MYSQL ? prisma.$transaction([ // @ts-test-if: provider !== Providers.MONGODB prisma.$executeRaw`INSERT INTO User (id, email) VALUES (${'2'}, ${'user_2@website.com'})`, // @ts-test-if: provider !== Providers.MONGODB prisma.$queryRaw`DELETE FROM User`, // @ts-test-if: provider !== Providers.MONGODB prisma.$executeRaw`INSERT INTO User (id, email) VALUES (${'1'}, ${'user_1@website.com'})`, // @ts-test-if: provider !== Providers.MONGODB prisma.$executeRaw`INSERT INTO User (id, email) VALUES (${'1'}, ${'user_1@website.com'})`, ]) : prisma.$transaction([ // @ts-test-if: provider !== Providers.MONGODB prisma.$executeRaw`INSERT INTO "User" (id, email) VALUES (${'2'}, ${'user_2@website.com'})`, // @ts-test-if: provider !== Providers.MONGODB prisma.$queryRaw`DELETE FROM "User"`, // @ts-test-if: provider !== Providers.MONGODB prisma.$executeRaw`INSERT INTO "User" (id, email) VALUES (${'1'}, ${'user_1@website.com'})`, // @ts-test-if: provider !== Providers.MONGODB prisma.$executeRaw`INSERT INTO "User" (id, email) VALUES (${'1'}, ${'user_1@website.com'})`, ]) await expect(result).rejects.toMatchPrismaErrorSnapshot() const users = await prisma.user.findMany() expect(users.length).toBe(1) }, ) /** * Two concurrent transactions should work */ test('concurrent', async () => { await Promise.all([ prisma.$transaction([ prisma.user.create({ data: { email: 'user_1@website.com', }, }), ]), prisma.$transaction([ prisma.user.create({ data: { email: 'user_2@website.com', }, }), ]), ]) const users = await prisma.user.findMany() expect(users.length).toBe(2) }) /** * Makes sure that the engine itself does not deadlock (regression test for https://github.com/prisma/prisma/issues/11750). * Issues on the database side are to be expected though: for SQLite, MySQL 8+ and MongoDB, it sometimes causes DB lock up * and all subsequent tests fail for some time. On SQL Server, the database kills the connections. */ testIf(provider === Providers.POSTGRESQL)('high concurrency with write conflicts', async () => { jest.setTimeout(30_000) await prisma.user.create({ data: { email: 'x', name: 'y', }, }) for (let i = 0; i < 5; i++) { await Promise.allSettled([ prisma.$transaction((tx) => tx.user.update({ data: { name: 'a' }, where: { email: 'x' } }), { timeout: 25, }), prisma.$transaction((tx) => tx.user.update({ data: { name: 'b' }, where: { email: 'x' } }), { timeout: 25, }), prisma.$transaction((tx) => tx.user.update({ data: { name: 'c' }, where: { email: 'x' } }), { timeout: 25, }), prisma.$transaction((tx) => tx.user.update({ data: { name: 'd' }, where: { email: 'x' } }), { timeout: 25, }), prisma.$transaction((tx) => tx.user.update({ data: { name: 'e' }, where: { email: 'x' } }), { timeout: 25, }), prisma.$transaction((tx) => tx.user.update({ data: { name: 'f' }, where: { email: 'x' } }), { timeout: 25, }), prisma.$transaction((tx) => tx.user.update({ data: { name: 'g' }, where: { email: 'x' } }), { timeout: 25, }), prisma.$transaction((tx) => tx.user.update({ data: { name: 'h' }, where: { email: 'x' } }), { timeout: 25, }), prisma.$transaction((tx) => tx.user.update({ data: { name: 'i' }, where: { email: 'x' } }), { timeout: 25, }), prisma.$transaction((tx) => tx.user.update({ data: { name: 'j' }, where: { email: 'x' } }), { timeout: 25, }), ]).catch(() => {}) // we don't care for errors, there will be } }) testIf(provider !== Providers.SQLITE)('high concurrency with no conflicts', async () => { jest.setTimeout(30_000) await prisma.user.create({ data: { email: 'x', name: 'y', }, }) // None of these transactions should fail. for (let i = 0; i < 5; i++) { await Promise.allSettled([ prisma.$transaction((tx) => tx.user.findMany()), prisma.$transaction((tx) => tx.user.findMany()), prisma.$transaction((tx) => tx.user.findMany()), prisma.$transaction((tx) => tx.user.findMany()), prisma.$transaction((tx) => tx.user.findMany()), prisma.$transaction((tx) => tx.user.findMany()), prisma.$transaction((tx) => tx.user.findMany()), prisma.$transaction((tx) => tx.user.findMany()), prisma.$transaction((tx) => tx.user.findMany()), prisma.$transaction((tx) => tx.user.findMany()), ]) } }) /** * Rollback should happen even with `then` calls */ test('rollback with then calls', async () => { const result = prisma.$transaction(async (prisma) => { await prisma.user .create({ data: { email: 'user_1@website.com', }, }) .then() await prisma.user .create({ data: { email: 'user_2@website.com', }, }) .then() .then() throw new Error('rollback') }) await expect(result).rejects.toThrowErrorMatchingInlineSnapshot(`"rollback"`) const users = await prisma.user.findMany() expect(users.length).toBe(0) }) /** * Rollback should happen even with `catch` calls */ test('rollback with catch calls', async () => { const result = prisma.$transaction(async (prisma) => { await prisma.user .create({ data: { email: 'user_1@website.com', }, }) .catch() await prisma.user .create({ data: { email: 'user_2@website.com', }, }) .catch() .then() throw new Error('rollback') }) await expect(result).rejects.toThrowErrorMatchingInlineSnapshot(`"rollback"`) const users = await prisma.user.findMany() expect(users.length).toBe(0) }) /** * Rollback should happen even with `finally` calls */ test('rollback with finally calls', async () => { const result = prisma.$transaction(async (prisma) => { await prisma.user .create({ data: { email: 'user_1@website.com', }, }) .finally() await prisma.user .create({ data: { email: 'user_2@website.com', }, }) .then() .catch() .finally() throw new Error('rollback') }) await expect(result).rejects.toThrowErrorMatchingInlineSnapshot(`"rollback"`) const users = await prisma.user.findMany() expect(users.length).toBe(0) }) /** * Makes sure that the engine can process when the transaction has locks inside * Engine PR - https://github.com/prisma/prisma-engines/pull/2811 * Issue - https://github.com/prisma/prisma/issues/11750 */ testIf(provider === Providers.POSTGRESQL)('high concurrency with SET FOR UPDATE', async () => { jest.setTimeout(60_000) const CONCURRENCY = 12 await prisma.user.create({ data: { email: 'x', name: 'y', val: 1, }, }) const promises = [...Array(CONCURRENCY)].map(() => prisma.$transaction( async (transactionPrisma) => { // @ts-test-if: provider !== Providers.MONGODB await transactionPrisma.$queryRaw`SELECT id from "User" where email = 'x' FOR UPDATE` const user = await transactionPrisma.user.findUniqueOrThrow({ where: { email: 'x', }, }) // Add a delay here to force the transaction to be open for longer // this will increase the chance of deadlock in the itx transactions // if deadlock is a possibility. await delay(100) const updatedUser = await transactionPrisma.user.update({ where: { email: 'x', }, data: { val: user.val! + 1, }, }) return updatedUser }, { timeout: 60_000, maxWait: 60_000 }, ), ) await Promise.allSettled(promises) const finalUser = await prisma.user.findUniqueOrThrow({ where: { email: 'x', }, }) expect(finalUser.val).toEqual(CONCURRENCY + 1) }) describeIf(provider !== Providers.MONGODB)('isolation levels', () => { function testIsolationLevel(title: string, supported: boolean, fn: () => Promise<void>) { test(title, async () => { if (supported) { await fn() } else { await expect(fn()).rejects.toThrow('Invalid enum value') } }) } testIsolationLevel('read committed', provider !== Providers.SQLITE, async () => { await prisma.$transaction( async (tx) => { await tx.user.create({ data: { email: 'user@example.com' } }) }, { // @ts-test-if: !['mongodb', 'sqlite'].includes(provider) isolationLevel: Prisma.TransactionIsolationLevel.ReadCommitted, }, ) await expect(prisma.user.findMany()).resolves.toHaveLength(1) }) testIsolationLevel( 'read uncommitted', provider !== Providers.SQLITE && provider !== Providers.COCKROACHDB, async () => { await prisma.$transaction( async (tx) => { await tx.user.create({ data: { email: 'user@example.com' } }) }, { // @ts-test-if: !['mongodb', 'sqlite', 'cockroachdb'].includes(provider) isolationLevel: Prisma.TransactionIsolationLevel.ReadUncommitted, }, ) await expect(prisma.user.findMany()).resolves.toHaveLength(1) }, ) testIsolationLevel( 'repeatable read', provider !== Providers.SQLITE && provider !== Providers.COCKROACHDB, async () => { await prisma.$transaction( async (tx) => { await tx.user.create({ data: { email: 'user@example.com' } }) }, { // @ts-test-if: !['mongodb', 'sqlite', 'cockroachdb'].includes(provider) isolationLevel: Prisma.TransactionIsolationLevel.RepeatableRead, }, ) await expect(prisma.user.findMany()).resolves.toHaveLength(1) }, ) testIsolationLevel('serializable', true, async () => { await prisma.$transaction( async (tx) => { await tx.user.create({ data: { email: 'user@example.com' } }) }, { // @ts-test-if: provider !== Providers.MONGODB isolationLevel: Prisma.TransactionIsolationLevel.Serializable, }, ) await expect(prisma.user.findMany()).resolves.toHaveLength(1) }) // TODO: there is also Snapshot level for sqlserver // it needs to be explicitly enabled on DB level and test setup can't do it at the moment // ref: https://docs.microsoft.com/en-us/troubleshoot/sql/analysis-services/enable-snapshot-transaction-isolation-level // testIsolationLevel('snapshot', provider === Providers.SQLSERVER, async () => { // await prisma.$transaction( // async (tx) => { // await tx.user.create({ data: { email: 'user@example.com' } }) // }, // { // // @ts-test-if: provider === Providers.SQLSERVER // isolationLevel: Prisma.TransactionIsolationLevel.Snapshot, // }, // ) // await expect(prisma.user.findMany()).resolves.toHaveLength(1) // }) test('invalid value', async () => { // @ts-test-if: provider === Providers.MONGODB const result = prisma.$transaction( async (tx) => { await tx.user.create({ data: { email: 'user@example.com' } }) }, { // @ts-test-if: provider !== Providers.MONGODB isolationLevel: 'NotAValidLevel', }, ) await expect(result).rejects.toMatchObject({ code: 'P2023', clientVersion: '0.0.0', }) await expect(result).rejects.toThrowErrorMatchingInlineSnapshot( `"Inconsistent column data: Conversion failed: Invalid isolation level \`NotAValidLevel\`"`, ) }) }) testIf(provider === Providers.MONGODB)('attempt to set isolation level on mongo', async () => { // @ts-test-if: provider === Providers.MONGODB const result = prisma.$transaction( async (tx) => { await tx.user.create({ data: { email: 'user@example.com' } }) }, { // @ts-test-if: provider !== Providers.MONGODB isolationLevel: 'CanBeAnything', }, ) await expect(result).rejects.toThrowErrorMatchingInlineSnapshot( `"The current database provider doesn't support a feature that the query used: Mongo does not support setting transaction isolation levels."`, ) }) }, { skipDriverAdapter: { from: [AdapterProviders.JS_D1, AdapterProviders.JS_LIBSQL], reason: 'js_d1: iTx are not possible. There is no Transaction API for D1 yet: https://github.com/cloudflare/workers-sdk/issues/2733; ' + 'js_libsql: SIGABRT crash occurs on having the first transaction with at least two create statements, panic inside `statement.rs` inside libsql', }, }, )

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/prisma/prisma'

If you have feedback or need assistance with the MCP directory API, please join our Discord server