From f23be14cab8edaaf94933165e16b49a47e1cecec Mon Sep 17 00:00:00 2001 From: Hashim1999164 <64767361+Hashim1999164@users.noreply.github.com> Date: Sat, 26 Sep 2026 23:46:34 +0500 Subject: [PATCH 1/2] fix(client): parameterize mixed batches that start with a raw query requestBatch skipped parameterization when the first item was raw, so RLS-style $executeRaw plus model query batches never used the plan cache. Signed-off-by: Hashim1999164 <64767361+Hashim1999164@users.noreply.github.com> --- .../parameterize-tests/batch.test.ts | 44 +++++++++++++++++++ .../core/engines/client/ClientEngine.ts | 3 +- .../engines/client/is-all-raw-batch.test.ts | 19 ++++++++ .../core/engines/client/is-all-raw-batch.ts | 9 ++++ 4 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 packages/client/src/runtime/core/engines/client/is-all-raw-batch.test.ts create mode 100644 packages/client/src/runtime/core/engines/client/is-all-raw-batch.ts diff --git a/packages/client-engine-runtime/src/parameterization/parameterize-tests/batch.test.ts b/packages/client-engine-runtime/src/parameterization/parameterize-tests/batch.test.ts index ca0b3e033d73..54341e33e371 100644 --- a/packages/client-engine-runtime/src/parameterization/parameterize-tests/batch.test.ts +++ b/packages/client-engine-runtime/src/parameterization/parameterize-tests/batch.test.ts @@ -406,4 +406,48 @@ describe('parameterizeBatch', () => { } `) }) + + it('parameterizes a model query that follows a raw statement', () => { + const batch: JsonBatchQuery = { + batch: [ + { + action: 'executeRaw', + query: { + arguments: { + query: "SELECT set_config('app.user_id', $1, true)", + parameters: '["1"]', + }, + selection: { $scalars: true }, + }, + }, + { + modelName: 'User', + action: 'findUnique', + query: { + arguments: { where: { id: 1 } }, + selection: { $scalars: true }, + }, + }, + ], + } + + const result = parameterizeBatch(batch, paramGraph) + + expect(result.parameterizedBatch.batch[0].query.arguments).toEqual({ + query: "SELECT set_config('app.user_id', $1, true)", + parameters: '["1"]', + }) + expect(result.parameterizedBatch.batch[1].query.arguments).toEqual({ + where: { + id: { + $type: 'Param', + value: { + name: '%1', + type: 'Int', + }, + }, + }, + }) + expect(result.placeholderValues).toEqual({ '%1': 1 }) + }) }) diff --git a/packages/client/src/runtime/core/engines/client/ClientEngine.ts b/packages/client/src/runtime/core/engines/client/ClientEngine.ts index 9e3157453ab2..4b0b69d31949 100644 --- a/packages/client/src/runtime/core/engines/client/ClientEngine.ts +++ b/packages/client/src/runtime/core/engines/client/ClientEngine.ts @@ -35,6 +35,7 @@ import { InteractiveTransactionInfo } from '../common/types/Transaction' import { getBatchRequestPayload } from '../common/utils/getBatchRequestPayload' import { getErrorMessageWithLink as genericGetErrorMessageWithLink } from '../common/utils/getErrorMessageWithLink' import type { Executor } from './Executor' +import { isAllRawBatch } from './is-all-raw-batch' import { LocalExecutor } from './LocalExecutor' import { QueryPlanCache } from './query-plan-cache' import { RemoteExecutor } from './RemoteExecutor' @@ -549,7 +550,7 @@ export class ClientEngine implements Engine { throw this.#transformRequestError(err, request) }) - const hasRawQueries = firstModelName === undefined + const hasRawQueries = isAllRawBatch(queries) let batchResponse: BatchResponse let placeholderValues: Record = {} let queryInfoQueries = queries.map((query) => query.query) diff --git a/packages/client/src/runtime/core/engines/client/is-all-raw-batch.test.ts b/packages/client/src/runtime/core/engines/client/is-all-raw-batch.test.ts new file mode 100644 index 000000000000..4b68ec24bd89 --- /dev/null +++ b/packages/client/src/runtime/core/engines/client/is-all-raw-batch.test.ts @@ -0,0 +1,19 @@ +import { isAllRawBatch } from './is-all-raw-batch' + +describe('isAllRawBatch', () => { + it('is true for an empty batch', () => { + expect(isAllRawBatch([])).toBe(true) + }) + + it('is true when every item is raw', () => { + expect(isAllRawBatch([{ action: 'executeRaw' }, { action: 'queryRaw' }])).toBe(true) + }) + + it('is false when a raw item is followed by a model query', () => { + expect(isAllRawBatch([{ action: 'executeRaw' }, { action: 'updateMany', modelName: 'Item' }])).toBe(false) + }) + + it('is false when a model query is first', () => { + expect(isAllRawBatch([{ action: 'updateMany', modelName: 'Item' }, { action: 'executeRaw' }])).toBe(false) + }) +}) diff --git a/packages/client/src/runtime/core/engines/client/is-all-raw-batch.ts b/packages/client/src/runtime/core/engines/client/is-all-raw-batch.ts new file mode 100644 index 000000000000..6b166c26e277 --- /dev/null +++ b/packages/client/src/runtime/core/engines/client/is-all-raw-batch.ts @@ -0,0 +1,9 @@ +/** + * Whether every query in a batch is raw (no `modelName`). + * + * Mixed batches such as `$executeRaw` followed by a model query must return + * false so the model query can still be parameterized and plan-cached. + */ +export function isAllRawBatch(queries: Array<{ modelName?: string }>): boolean { + return queries.every((query) => query.modelName === undefined) +} From e55b34055e4faa96e9dec4b5a27eb1fd7cc3e838 Mon Sep 17 00:00:00 2001 From: Hashim Khan <64767361+Hashim1999164@users.noreply.github.com> Date: Sun, 27 Sep 2026 05:41:11 +0500 Subject: [PATCH 2/2] Include action in isAllRawBatch query type Signed-off-by: Hashim Khan <64767361+Hashim1999164@users.noreply.github.com> --- .../client/src/runtime/core/engines/client/is-all-raw-batch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/runtime/core/engines/client/is-all-raw-batch.ts b/packages/client/src/runtime/core/engines/client/is-all-raw-batch.ts index 6b166c26e277..c1dded68dc08 100644 --- a/packages/client/src/runtime/core/engines/client/is-all-raw-batch.ts +++ b/packages/client/src/runtime/core/engines/client/is-all-raw-batch.ts @@ -4,6 +4,6 @@ * Mixed batches such as `$executeRaw` followed by a model query must return * false so the model query can still be parameterized and plan-cached. */ -export function isAllRawBatch(queries: Array<{ modelName?: string }>): boolean { +export function isAllRawBatch(queries: Array<{ action: string; modelName?: string }>): boolean { return queries.every((query) => query.modelName === undefined) }