Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/client-engine-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@prisma/debug": "workspace:*",
"@prisma/driver-adapter-utils": "workspace:*",
"@prisma/sqlcommenter": "workspace:*",
"klona": "2.0.6",
"nanoid": "5.1.5",
"ulid": "3.0.0",
"uuid": "11.1.0"
Expand Down
91 changes: 91 additions & 0 deletions packages/client-engine-runtime/src/sql-commenter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,97 @@ describe('applySqlCommenters', () => {
expect(applySqlCommenters([plugin], mockSingleContext)).toEqual({ type: 'single' })
expect(applySqlCommenters([plugin], mockCompactedContext)).toEqual({ type: 'compacted' })
})

test('each plugin receives an isolated copy of context', () => {
let firstPluginContext: SqlCommenterContext | undefined
let secondPluginContext: SqlCommenterContext | undefined

const firstPlugin: SqlCommenterPlugin = (ctx) => {
firstPluginContext = ctx
Object.assign(ctx, { mutated: true })
Object.assign(ctx.query, { modelName: 'Hacked' })
return { first: 'plugin' }
}

const secondPlugin: SqlCommenterPlugin = (ctx) => {
secondPluginContext = ctx
return { second: 'plugin' }
}

applySqlCommenters([firstPlugin, secondPlugin], mockSingleContext)

expect(firstPluginContext).not.toBe(secondPluginContext)
expect(secondPluginContext!.query.modelName).toBe('User')
expect(mockSingleContext.query.modelName).toBe('User')
expect((mockSingleContext as unknown as Record<string, unknown>).mutated).toBeUndefined()
})

test('deeply nested mutations do not affect other plugins', () => {
type NestedQuery = { selection: { name: boolean; nested: { deep: string } } }

const contextWithNestedQuery: SqlCommenterContext = {
query: {
type: 'single',
modelName: 'User',
action: 'findMany',
query: { selection: { name: true, nested: { deep: 'value' } } } satisfies NestedQuery,
},
}

let secondPluginDeepValue: string | undefined

const firstPlugin: SqlCommenterPlugin = (ctx) => {
if (ctx.query.type === 'single') {
const query = ctx.query.query as NestedQuery
query.selection.nested.deep = 'mutated'
}
return { first: 'plugin' }
}

const secondPlugin: SqlCommenterPlugin = (ctx) => {
if (ctx.query.type === 'single') {
const query = ctx.query.query as NestedQuery
secondPluginDeepValue = query.selection.nested.deep
}
return { second: 'plugin' }
}

applySqlCommenters([firstPlugin, secondPlugin], contextWithNestedQuery)

expect(secondPluginDeepValue).toBe('value')

const originalQuery = contextWithNestedQuery.query
if (originalQuery.type === 'single') {
expect((originalQuery.query as NestedQuery).selection.nested.deep).toBe('value')
}
})

test('array mutations do not affect other plugins', () => {
let secondPluginQueriesLength: number | undefined

const firstPlugin: SqlCommenterPlugin = (ctx) => {
if (ctx.query.type === 'compacted') {
const mutableQueries = ctx.query.queries as unknown[]
mutableQueries.push({ extra: 'query' })
}
return { first: 'plugin' }
}

const secondPlugin: SqlCommenterPlugin = (ctx) => {
if (ctx.query.type === 'compacted') {
secondPluginQueriesLength = ctx.query.queries.length
}
return { second: 'plugin' }
}

applySqlCommenters([firstPlugin, secondPlugin], mockCompactedContext)

expect(secondPluginQueriesLength).toBe(2)

if (mockCompactedContext.query.type === 'compacted') {
expect(mockCompactedContext.query.queries.length).toBe(2)
}
})
})

describe('buildSqlComment', () => {
Expand Down
8 changes: 6 additions & 2 deletions packages/client-engine-runtime/src/sql-commenter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { SqlCommenterContext, SqlCommenterPlugin } from '@prisma/sqlcommenter'
import { klona } from 'klona'

/**
* Formats key-value pairs into a sqlcommenter-compatible comment string.
Expand All @@ -11,7 +12,7 @@ import type { SqlCommenterContext, SqlCommenterPlugin } from '@prisma/sqlcomment
* 5. Replace ' with \' in values (after URL encoding)
* 6. Wrap values in single quotes
* 7. Join key='value' pairs with commas
* 8. Wrap in /* *\/
* 8. Wrap in a SQL comment
*/
export function formatSqlComment(tags: Record<string, string>): string {
const entries = Object.entries(tags)
Expand All @@ -34,6 +35,9 @@ export function formatSqlComment(tags: Record<string, string>): string {
/**
* Applies SQL commenter plugins and returns the merged key-value pairs.
* Keys with undefined values are filtered out.
*
* Each plugin receives a deep clone of the context to prevent mutations
* that could affect other plugins.
*/
export function applySqlCommenters(
plugins: SqlCommenterPlugin[],
Expand All @@ -42,7 +46,7 @@ export function applySqlCommenters(
const merged: Record<string, string> = {}

for (const plugin of plugins) {
const tags = plugin(context)
const tags = plugin(klona(context))
for (const [key, value] of Object.entries(tags)) {
if (value !== undefined) {
merged[key] = value
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading