render-query.test.ts•18.4 kB
import type { PlaceholderFormat, QueryPlanDbQuery } from '../query-plan'
import { GeneratorRegistry } from './generators'
import { renderQuery } from './render-query'
import { ScopeBindings } from './scope'
const TEST_MAX_CHUNK_SIZE = 10
test('no template', () => {
expect(
renderQuery(
{
type: 'rawSql',
sql: 'SELECT * FROM users WHERE id = $1',
args: [1],
argTypes: [{ arity: 'scalar', scalarType: 'int' }],
} satisfies QueryPlanDbQuery,
{} as ScopeBindings,
{},
),
).toEqual([
{
sql: 'SELECT * FROM users WHERE id = $1',
args: [1],
argTypes: [{ arity: 'scalar', scalarType: 'int' }],
},
])
})
test('no template and scalar list parameter', () => {
expect(
renderQuery(
{
type: 'templateSql',
fragments: [
{ type: 'stringChunk', chunk: 'SELECT * FROM users WHERE id = ' },
{ type: 'parameter' },
{ type: 'stringChunk', chunk: ' AND numbers = ' },
{ type: 'parameter' },
],
placeholderFormat: {
prefix: '$',
hasNumbering: true,
} satisfies PlaceholderFormat,
args: [1, [1, 2, 3]],
argTypes: [
{ arity: 'scalar', scalarType: 'int' },
{ arity: 'list', scalarType: 'int' },
],
chunkable: true,
} satisfies QueryPlanDbQuery,
{} as ScopeBindings,
{},
),
).toEqual([
{
sql: 'SELECT * FROM users WHERE id = $1 AND numbers = $2',
args: [1, [1, 2, 3]],
argTypes: [
{ arity: 'scalar', scalarType: 'int' },
{ arity: 'list', scalarType: 'int' },
],
},
])
})
test('transforms IN template', () => {
expect(
renderQuery(
{
type: 'templateSql',
fragments: [
{ type: 'stringChunk', chunk: 'SELECT * FROM users WHERE "userId" IN ' },
{ type: 'parameterTuple' },
{ type: 'stringChunk', chunk: ' OFFSET ' },
{ type: 'parameter' },
],
placeholderFormat: {
prefix: '$',
hasNumbering: true,
} satisfies PlaceholderFormat,
args: [[1, 2, 3], 0],
argTypes: [
{ arity: 'scalar', scalarType: 'int' },
{ arity: 'scalar', scalarType: 'int' },
],
chunkable: true,
} satisfies QueryPlanDbQuery,
{} as ScopeBindings,
{},
),
).toEqual([
{
sql: 'SELECT * FROM users WHERE "userId" IN ($1,$2,$3) OFFSET $4',
args: [1, 2, 3, 0],
argTypes: Array.from({ length: 4 }, () => ({ arity: 'scalar', scalarType: 'int' })),
},
])
})
test('transforms IN template with empty list', () => {
expect(
renderQuery(
{
type: 'templateSql',
fragments: [
{ type: 'stringChunk', chunk: 'SELECT * FROM users WHERE "userId" IN ' },
{ type: 'parameterTuple' },
{ type: 'stringChunk', chunk: ' OFFSET ' },
{ type: 'parameter' },
],
placeholderFormat: {
prefix: '$',
hasNumbering: true,
} satisfies PlaceholderFormat,
args: [[], 0],
argTypes: [
{ arity: 'scalar', scalarType: 'int' },
{ arity: 'scalar', scalarType: 'int' },
],
chunkable: true,
} satisfies QueryPlanDbQuery,
{} as ScopeBindings,
{},
),
).toEqual([
{
sql: 'SELECT * FROM users WHERE "userId" IN (NULL) OFFSET $1',
args: [0],
argTypes: [{ arity: 'scalar', scalarType: 'int' }],
},
])
})
test('handles singleton list in IN template', () => {
expect(
renderQuery(
{
type: 'templateSql',
fragments: [
{ type: 'stringChunk', chunk: 'SELECT * FROM users WHERE "userId" IN ' },
{ type: 'parameterTuple' },
{ type: 'stringChunk', chunk: ' OFFSET ' },
{ type: 'parameter' },
],
placeholderFormat: {
prefix: '$',
hasNumbering: true,
} satisfies PlaceholderFormat,
args: [[1], 0],
argTypes: Array.from({ length: 2 }, () => ({ arity: 'scalar', scalarType: 'int' })),
chunkable: true,
} satisfies QueryPlanDbQuery,
{} as ScopeBindings,
{},
),
).toEqual([
{
sql: 'SELECT * FROM users WHERE "userId" IN ($1) OFFSET $2',
args: [1, 0],
argTypes: Array.from({ length: 2 }, () => ({ arity: 'scalar', scalarType: 'int' })),
},
])
})
test('treats non-array element as a singleton list in IN template', () => {
expect(
renderQuery(
{
type: 'templateSql',
fragments: [
{ type: 'stringChunk', chunk: 'SELECT * FROM users WHERE "userId" IN ' },
{ type: 'parameterTuple' },
{ type: 'stringChunk', chunk: ' OFFSET ' },
{ type: 'parameter' },
],
placeholderFormat: {
prefix: '$',
hasNumbering: true,
} satisfies PlaceholderFormat,
args: [1, 0],
argTypes: Array.from({ length: 2 }, () => ({ arity: 'scalar', scalarType: 'int' })),
chunkable: true,
} satisfies QueryPlanDbQuery,
{} as ScopeBindings,
{},
),
).toEqual([
{
sql: 'SELECT * FROM users WHERE "userId" IN ($1) OFFSET $2',
args: [1, 0],
argTypes: Array.from({ length: 2 }, () => ({ arity: 'scalar', scalarType: 'int' })),
},
])
})
test("transforms IN template, doesn't touch scalar list", () => {
expect(
renderQuery(
{
type: 'templateSql',
fragments: [
{ type: 'stringChunk', chunk: 'SELECT * FROM users WHERE "userId" IN ' },
{ type: 'parameterTuple' },
{ type: 'stringChunk', chunk: ' AND numbers = ' },
{ type: 'parameter' },
{ type: 'stringChunk', chunk: ' OFFSET ' },
{ type: 'parameter' },
],
placeholderFormat: {
prefix: '$',
hasNumbering: true,
} satisfies PlaceholderFormat,
args: [[1, 2, 3], [1, 2, 3], 0],
argTypes: [
{ arity: 'scalar', scalarType: 'int' },
{ arity: 'list', scalarType: 'int' },
{ arity: 'scalar', scalarType: 'int' },
],
chunkable: true,
} satisfies QueryPlanDbQuery,
{} as ScopeBindings,
{},
),
).toEqual([
{
sql: 'SELECT * FROM users WHERE "userId" IN ($1,$2,$3) AND numbers = $4 OFFSET $5',
args: [1, 2, 3, [1, 2, 3], 0],
argTypes: [
...Array.from({ length: 3 }, () => ({ arity: 'scalar', scalarType: 'int' })),
{ arity: 'list', scalarType: 'int' },
{ arity: 'scalar', scalarType: 'int' },
],
},
])
})
test('transforms INSERT VALUES template', () => {
expect(
renderQuery(
{
type: 'templateSql',
fragments: [
{ type: 'stringChunk', chunk: 'INSERT INTO "public"."_CategoryToPost" ("A", "B") VALUES ' },
{
type: 'parameterTupleList',
itemPrefix: '(',
itemSeparator: ',',
itemSuffix: ')',
groupSeparator: ',',
},
],
placeholderFormat: {
prefix: '$',
hasNumbering: true,
} satisfies PlaceholderFormat,
args: [
[
[1, 2],
[3, 4],
],
],
argTypes: Array.from({ length: 2 }, () => ({ arity: 'scalar', scalarType: 'int' })),
chunkable: true,
} satisfies QueryPlanDbQuery,
{} as ScopeBindings,
{},
),
).toEqual([
{
sql: 'INSERT INTO "public"."_CategoryToPost" ("A", "B") VALUES ($1,$2),($3,$4)',
args: [1, 2, 3, 4],
argTypes: Array.from({ length: 4 }, () => ({ arity: 'scalar', scalarType: 'int' })),
},
])
})
test('chunking an INSERT with a large parameterTupleList', () => {
expect(
renderQuery(
{
type: 'templateSql',
fragments: [
{ type: 'stringChunk', chunk: 'INSERT INTO "public"."Table" VALUES ' },
{
type: 'parameterTupleList',
itemPrefix: '(',
itemSeparator: ',',
itemSuffix: ')',
groupSeparator: ',',
},
],
placeholderFormat: {
prefix: '$',
hasNumbering: true,
} satisfies PlaceholderFormat,
args: [
[
Array.from({ length: 5 }, (_, i) => i + 1),
Array.from({ length: 5 }, (_, i) => i + 6),
Array.from({ length: 5 }, (_, i) => i + 11),
Array.from({ length: 5 }, (_, i) => i + 16),
Array.from({ length: 5 }, (_, i) => i + 21),
],
],
argTypes: Array.from({ length: 5 }, () => ({ arity: 'scalar', scalarType: 'int' })),
chunkable: true,
} satisfies QueryPlanDbQuery,
{} as ScopeBindings,
{},
TEST_MAX_CHUNK_SIZE,
),
).toMatchObject([
{
sql: expect.stringMatching(/^INSERT INTO "public"\."Table" VALUES (\((\$[0-9]+,?){5}\),?){2}$/),
args: Array.from({ length: 10 }, (_, i) => i + 1),
argTypes: Array.from({ length: 10 }, () => ({ arity: 'scalar', scalarType: 'int' })),
},
{
sql: expect.stringMatching(/^INSERT INTO "public"\."Table" VALUES (\((\$[0-9]+,?){5}\),?){2}$/),
args: Array.from({ length: 10 }, (_, i) => i + 11),
argTypes: Array.from({ length: 10 }, () => ({ arity: 'scalar', scalarType: 'int' })),
},
{
sql: expect.stringMatching(/^INSERT INTO "public"\."Table" VALUES (\((\$[0-9]+,?){5}\),?){1}$/),
args: Array.from({ length: 5 }, (_, i) => i + 21),
argTypes: Array.from({ length: 5 }, () => ({ arity: 'scalar', scalarType: 'int' })),
},
])
})
test('chunking a UNION ALL with a large parameterTupleList', () => {
expect(
renderQuery(
{
type: 'templateSql',
fragments: [
{ type: 'stringChunk', chunk: 'SELECT * FROM [User] WHERE "banned" = ' },
{ type: 'parameter' },
{ type: 'stringChunk', chunk: ' AND "id" IN (SELECT ' },
{
type: 'parameterTupleList',
itemPrefix: '',
itemSeparator: ',',
itemSuffix: '',
groupSeparator: ' UNION ALL SELECT ',
},
{ type: 'stringChunk', chunk: ') AND ("name" = ' },
{ type: 'parameter' },
{ type: 'stringChunk', chunk: ' OR "name" = ' },
{ type: 'parameter' },
{ type: 'stringChunk', chunk: ')' },
],
placeholderFormat: {
prefix: '$',
hasNumbering: true,
} satisfies PlaceholderFormat,
args: [false, Array.from({ length: 5 }, (_, i) => [i + 1, i + 2]), 'John Doe', 'Jane Doe'],
argTypes: [
{ arity: 'scalar', scalarType: 'boolean' },
{ arity: 'scalar', scalarType: 'int' },
{ arity: 'scalar', scalarType: 'string' },
{ arity: 'scalar', scalarType: 'string' },
],
chunkable: true,
} satisfies QueryPlanDbQuery,
{} as ScopeBindings,
{},
TEST_MAX_CHUNK_SIZE,
),
).toMatchObject([
{
sql: expect.stringMatching(
/^SELECT \* FROM \[User\] WHERE "banned" = \$1 AND "id" IN \(SELECT ((\$[0-9]+,?){2}( UNION ALL SELECT )?){3}\) AND \("name" = \$8 OR "name" = \$9\)$/,
),
args: [false, ...Array.from({ length: 3 }, (_, i) => [i + 1, i + 2]).flat(), 'John Doe', 'Jane Doe'],
argTypes: [
{ arity: 'scalar', scalarType: 'boolean' },
...Array.from({ length: 6 }, () => ({ arity: 'scalar', scalarType: 'int' })),
{ arity: 'scalar', scalarType: 'string' },
{ arity: 'scalar', scalarType: 'string' },
],
},
{
sql: expect.stringMatching(
/^SELECT \* FROM \[User\] WHERE "banned" = \$1 AND "id" IN \(SELECT ((\$[0-9]+,?){2}( UNION ALL SELECT )?){2}\) AND \("name" = \$6 OR "name" = \$7\)$/,
),
args: [false, ...Array.from({ length: 2 }, (_, i) => [i + 4, i + 5]).flat(), 'John Doe', 'Jane Doe'],
argTypes: [
{ arity: 'scalar', scalarType: 'boolean' },
...Array.from({ length: 4 }, () => ({ arity: 'scalar', scalarType: 'int' })),
{ arity: 'scalar', scalarType: 'string' },
{ arity: 'scalar', scalarType: 'string' },
],
},
])
})
test('chunking a SELECT..IN with a large parameterTuple', () => {
expect(
renderQuery(
{
type: 'templateSql',
fragments: [
{ type: 'stringChunk', chunk: 'SELECT FROM "public"."User" WHERE "banned" = ' },
{ type: 'parameter' },
{ type: 'stringChunk', chunk: ' AND "id" IN ' },
{ type: 'parameterTuple' },
{ type: 'stringChunk', chunk: ' AND "name" = ' },
{ type: 'parameter' },
],
placeholderFormat: {
prefix: '$',
hasNumbering: true,
} satisfies PlaceholderFormat,
args: [false, Array.from({ length: 10 }, (_, i) => i + 1), 'John Doe'],
argTypes: [
{ arity: 'scalar', scalarType: 'boolean' },
{ arity: 'scalar', scalarType: 'int' },
{ arity: 'scalar', scalarType: 'string' },
],
chunkable: true,
} satisfies QueryPlanDbQuery,
{} as ScopeBindings,
{},
TEST_MAX_CHUNK_SIZE,
),
).toMatchObject([
{
sql: expect.stringMatching(
/^SELECT FROM "public"\."User" WHERE "banned" = \$1 AND "id" IN \((\$[0-9]+,?){8}\) AND "name" = \$10$/,
),
args: [false, ...Array.from({ length: 8 }, (_, i) => i + 1), 'John Doe'],
argTypes: [
{ arity: 'scalar', scalarType: 'boolean' },
...Array.from({ length: 8 }, () => ({ arity: 'scalar', scalarType: 'int' })),
{ arity: 'scalar', scalarType: 'string' },
],
},
{
sql: expect.stringMatching(
/^SELECT FROM "public"\."User" WHERE "banned" = \$1 AND "id" IN \((\$[0-9]+,?){2}\) AND "name" = \$4/,
),
args: [false, ...Array.from({ length: 2 }, (_, i) => i + 9), 'John Doe'],
argTypes: [
{ arity: 'scalar', scalarType: 'boolean' },
...Array.from({ length: 2 }, () => ({ arity: 'scalar', scalarType: 'int' })),
{ arity: 'scalar', scalarType: 'string' },
],
},
])
})
test('chunking a SELECT..IN with multiple parameterTuples', () => {
expect(
renderQuery(
{
type: 'templateSql',
fragments: [
{ type: 'stringChunk', chunk: 'SELECT FROM "public"."User" WHERE "id" IN ' },
{
type: 'parameterTuple',
},
{ type: 'stringChunk', chunk: ' AND "age" IN ' },
{ type: 'parameterTuple' },
],
placeholderFormat: {
prefix: '$',
hasNumbering: true,
} satisfies PlaceholderFormat,
args: [Array.from({ length: 10 }, (_, i) => i + 1), Array.from({ length: 4 }, (_, i) => i + 1)],
argTypes: [
{ arity: 'scalar', scalarType: 'int' },
{ arity: 'scalar', scalarType: 'int' },
{ arity: 'scalar', scalarType: 'int' },
],
chunkable: true,
} satisfies QueryPlanDbQuery,
{} as ScopeBindings,
{},
TEST_MAX_CHUNK_SIZE,
),
).toMatchObject([
{
sql: expect.stringMatching(
/^SELECT FROM "public"\."User" WHERE "id" IN \((\$[0-9]+,?){6}\) AND "age" IN \((\$[0-9]+,?){4}\)$/,
),
args: [...Array.from({ length: 6 }, (_, i) => i + 1), ...Array.from({ length: 4 }, (_, i) => i + 1)],
argTypes: Array.from({ length: 10 }, () => ({ arity: 'scalar', scalarType: 'int' })),
},
{
sql: expect.stringMatching(
/^SELECT FROM "public"\."User" WHERE "id" IN \((\$[0-9]+,?){4}\) AND "age" IN \((\$[0-9]+,?){4}\)$/,
),
args: [...Array.from({ length: 4 }, (_, i) => i + 7), ...Array.from({ length: 4 }, (_, i) => i + 1)],
argTypes: Array.from({ length: 8 }, () => ({ arity: 'scalar', scalarType: 'int' })),
},
])
})
test('a SELECT..IN with a large parameterTuple that is not chunkable', () => {
expect(
renderQuery(
{
type: 'templateSql',
fragments: [
{ type: 'stringChunk', chunk: 'SELECT FROM "public"."User" WHERE "banned" = ' },
{ type: 'parameter' },
{ type: 'stringChunk', chunk: ' AND "id" IN ' },
{ type: 'parameterTuple' },
{ type: 'stringChunk', chunk: ' AND "name" = ' },
{ type: 'parameter' },
],
placeholderFormat: {
prefix: '$',
hasNumbering: true,
} satisfies PlaceholderFormat,
args: [false, Array.from({ length: 3000 }, (_, i) => i + 1), 'John Doe'],
argTypes: [
{ arity: 'scalar', scalarType: 'boolean' },
{ arity: 'scalar', scalarType: 'int' },
{ arity: 'scalar', scalarType: 'string' },
],
chunkable: false,
} satisfies QueryPlanDbQuery,
{} as ScopeBindings,
{},
TEST_MAX_CHUNK_SIZE,
),
).toMatchObject([
{
sql: expect.stringMatching(
/^SELECT FROM "public"\."User" WHERE "banned" = \$1 AND "id" IN \((\$[0-9]+,?){3000}\) AND "name" = \$3002$/,
),
args: [false, ...Array.from({ length: 3000 }, (_, i) => i + 1), 'John Doe'],
argTypes: [
{ arity: 'scalar', scalarType: 'boolean' },
...Array.from({ length: 3000 }, () => ({ arity: 'scalar', scalarType: 'int' })),
{ arity: 'scalar', scalarType: 'string' },
],
},
])
})
test('executes a generator', () => {
const generators = new GeneratorRegistry()
expect(
renderQuery(
{
type: 'rawSql',
sql: 'INSERT INTO users (id, name) VALUES ($1, $2)',
args: [
{ prisma__type: 'generatorCall', prisma__value: { name: 'uuid', args: [4] } },
{ prisma__type: 'generatorCall', prisma__value: { name: 'now', args: [] } },
],
argTypes: [
{ arity: 'scalar', scalarType: 'datetime' },
{ arity: 'scalar', scalarType: 'datetime' },
],
} satisfies QueryPlanDbQuery,
{} as ScopeBindings,
generators.snapshot(),
),
).toMatchObject([
{
sql: 'INSERT INTO users (id, name) VALUES ($1, $2)',
args: [expect.any(String), expect.any(String)],
argTypes: [
{ arity: 'scalar', scalarType: 'datetime' },
{ arity: 'scalar', scalarType: 'datetime' },
],
},
])
})