tests.ts•7.57 kB
import { Providers } from '../_utils/providers'
import testMatrix from './_matrix'
import {
EXCESS_BIND_VALUES_BY_PROVIDER,
MAX_BIND_VALUES_BY_PROVIDER,
RELATION_JOINS_NO_CHUNKING_ERROR_MSG,
} from './_utils'
// @ts-ignore
import type { PrismaClient, Tag } from './generated/prisma/client'
declare let prisma: PrismaClient
testMatrix.setupTestSuite(
({ provider, driverAdapter, clientEngineExecutor }, _suiteMeta, { runtime }, cliMeta) => {
const MAX_BIND_VALUES = MAX_BIND_VALUES_BY_PROVIDER[provider]
const EXCESS_BIND_VALUES = EXCESS_BIND_VALUES_BY_PROVIDER[provider]
const usesJsDrivers = driverAdapter !== undefined || clientEngineExecutor === 'remote'
function generatedIds(n: number) {
// ["1","2",...,"n"]
const ids = Array.from({ length: n }, (_, i) => i + 1)
return ids
}
const usingRelationJoins = cliMeta.previewFeatures.includes('relationJoins')
// Chunking is not supported with joins, so the tests for success cases need to be skipped
describeIf(!usingRelationJoins)('issues #8832 / #9326 success cases', () => {
async function createTags(n: number): Promise<number[]> {
const ids = generatedIds(n)
const data = ids.map((id) => ({ id }))
await prisma.tag.createMany({
data,
})
return ids
}
async function getTagsFindManyInclude(): Promise<Tag[]> {
const tags = await prisma.tag.findMany({
include: {
posts: true,
},
})
return tags
}
async function getTagsParams(ids: number[]): Promise<Tag[]> {
const idsParams = ids.map((paramIdx) => {
const param = ['mysql', 'sqlite'].includes(provider) ? '?' : `\$${paramIdx}`
return param
})
const tags = await prisma.$queryRawUnsafe<Tag[]>(
`
SELECT *
FROM tag
WHERE id IN (${idsParams.join(', ')})
`,
...ids,
)
return tags
}
async function getTagsFindManyIn(ids: number[]): Promise<Tag[]> {
const tags = await prisma.tag.findMany({
where: {
id: {
in: ids,
},
},
})
return tags
}
async function clean() {
await prisma.$transaction([prisma.tagsOnPosts.deleteMany(), prisma.post.deleteMany(), prisma.tag.deleteMany()])
}
afterEach(async () => {
await clean()
}, 80_000)
test('should succeed when "in" has MAX ids', async () => {
const ids = await createTags(MAX_BIND_VALUES)
const tags = await getTagsFindManyIn(ids)
expect(tags.length).toBe(MAX_BIND_VALUES)
})
test('should succeed when "include" involves MAX records', async () => {
await createTags(MAX_BIND_VALUES)
const tags = await getTagsFindManyInclude()
expect(tags.length).toBe(MAX_BIND_VALUES)
})
test('should succeed when "in" has EXCESS ids', async () => {
const ids = await createTags(EXCESS_BIND_VALUES)
const tags = await getTagsFindManyIn(ids)
expect(tags.length).toBe(EXCESS_BIND_VALUES)
})
test('should succeed when "include" involves EXCESS records', async () => {
await createTags(EXCESS_BIND_VALUES)
const tags = await getTagsFindManyInclude()
expect(tags.length).toBe(EXCESS_BIND_VALUES)
})
// Chunking mechanism looks flawed with pagination.
// See https://github.com/prisma/prisma/issues/23733 for more info
// eslint-disable-next-line jest/no-disabled-tests
test.skip('should succeed when "in" has EXCESS ids and a "skip" filter', async () => {
const ids = await createTags(EXCESS_BIND_VALUES)
const tags = await prisma.tag.findMany({
where: {
id: { in: ids },
},
skip: 1,
})
expect(tags.length).toBe(EXCESS_BIND_VALUES - 1)
})
test('should succeed when raw query has MAX ids', async () => {
const ids = await createTags(MAX_BIND_VALUES)
const tags = await getTagsParams(ids)
expect(tags.length).toBe(MAX_BIND_VALUES)
})
// Sqlite excluded because it's bind parameter limit is currently incorrect which makes the QE chunk queries enough to not trigger the error.
testIf(!usesJsDrivers && provider !== Providers.SQLITE)('should fail when raw query has EXCESS ids', async () => {
const ids = await createTags(EXCESS_BIND_VALUES)
await expect(getTagsParams(ids)).rejects.toThrow()
})
})
// Sqlite excluded because it's bind parameter limit is currently incorrect which makes the QE chunk queries enough to not trigger the error.
//
// See: https://github.com/prisma/prisma/issues/21802.
// See: https://github.com/prisma/prisma/issues/21803.
describeIf(provider !== Providers.SQLITE)('chunking logic does not trigger with 2 IN filters', () => {
function selectWith2InFilters(ids: number[]) {
return prisma.tag.findMany({
where: {
OR: [
{
id: { in: ids },
},
{
id: { in: ids },
},
],
},
})
}
test('Selecting MAX ids at once in two inclusive disjunct filters results in error', async () => {
const ids = generatedIds(MAX_BIND_VALUES)
if (!usesJsDrivers || (runtime === 'client' && clientEngineExecutor === 'local')) {
// When using MAX ids, it fails both with relationJoins and without because the amount of query params that's computed is not beyond the limit.
// To be clear: the root problem comes from the way the QE computes the amount of query params.
await expect(selectWith2InFilters(ids)).rejects.toThrow()
} else {
// It's unknown why this test doesn't fail with driver adapters.
await expect(selectWith2InFilters(ids)).resolves.toMatchInlineSnapshot(`[]`)
}
})
test('Selecting EXCESS ids at once in two inclusive disjunct filters results in error', async () => {
const ids = generatedIds(EXCESS_BIND_VALUES)
if (usingRelationJoins) {
await expect(selectWith2InFilters(ids)).rejects.toThrow(RELATION_JOINS_NO_CHUNKING_ERROR_MSG)
} else {
await expect(selectWith2InFilters(ids)).rejects.toThrow()
}
})
})
},
{
optOut: {
from: ['sqlserver', 'mongodb'],
reason: 'not relevant for this test.',
},
skipDriverAdapter: {
from: ['js_planetscale', 'js_neon', 'js_d1', 'js_mariadb'],
// `rpc error: code = Aborted desc = Row count exceeded 10000 (CallerID: userData1)", state: "70100"`
// This could potentially be configured in Vitess by increasing the `queryserver-config-max-result-size`
// query server parameter.
reason:
'Vitess supports at most 10k rows returned in a single query, so this test is not applicable. ' +
'Neon occasionally fails with different parameter counts in its error messages. ' +
'D1 does not have the correct amount of max_bind_values.' +
'The query appears to raise no error with the MariaDB driver adapter.',
},
skip(when, { clientEngineExecutor, provider }) {
when(
clientEngineExecutor === 'remote' && provider === Providers.MYSQL,
`
Query plan executor server uses MariaDB driver internally.
`,
)
},
},
)