Replies: 6 comments 5 replies
|
Hi @dennemark 👋 You code looks good for the most parts. You can include error handling for the Prisma.defineExtension((client) => {
return client.$extends({
query: {
$allModels: {
async $allOperations<T>({ args, query, model, operation, ...rest }: { args: any, query: any, model: any, operation: any }) {
const setRLS = async (client: any) => {
try {
await client.$executeRaw('some rls logic...')
} catch (error) {
console.error('RLS logic failed:', error)
throw new Error('Failed to set RLS')
}
}
const checkResults = (result: any) => {
if (result === 'not-valid') {
throw new Error('This is just an example check function...')
}
return result
}
// Check if this query already runs within a transaction
if ((rest as any).__internalParams.transaction) {
const transaction = (rest as any).__internalParams.transaction
if (transaction.kind === 'itx') {
// We are in an interactive transaction and create a client for it
const transactionClient = (client as any)._createItxClient(transaction)
await setRLS(transactionClient)
return transactionClient[model][operation](args).then(checkResults)
} else if (transaction.kind === 'batch') {
throw new Error('Sequential transactions are not supported in Prisma client extensions.')
}
} else {
// If we are not in a transaction, we can run our own
return client.$transaction(async (tx) => {
await setRLS(tx)
// We place our check results function in an interactive transaction
// so that our query is rolled back if check results fail
return tx[model][operation](args).then(checkResults)
})
}
},
},
}
})
}) |
|
Thanks for sharing this snippet! I've tweaked on it a bit to also cover const prisma = new PrismaClient()
const setupRls = (config: string, value: string) => async ({
query,
args,
model,
operation,
...rest
}: any) => {
const setRlsConfig = (client: PrismaClient) => {
return client.$executeRaw`SELECT set_config(${config}, ${value}, TRUE)`
}
const transaction = (rest as any).__internalParams.transaction
if (transaction) {
if (transaction.kind !== 'itx') {
throw new Error('Non itx transaction')
}
const transactionClient = (prisma as any)._createItxClient(transaction)
await setRlsConfig(transactionClient)
if (model) {
return transactionClient[model][operation](args)
}
return transactionClient[operation](args)
}
const [, result] = await prisma.$transaction([
setRlsConfig(prisma),
query(args),
])
return result
}This way, I create a prisma client for a specific tenant whenever I need and RLS cover also raw queries const PG_COMPANY_ID_RLS_CONFIG = 'app.current_company_id'
export const getCompanyPrisma = (companyId: Company['id']) => {
const extension = Prisma.defineExtension((prisma) =>
prisma.$extends({
query: {
// don't use it for $allModels, to cover $queryRaw, $queryRawUnsafe, $executeRaw and $executeRawUnsafe
$allOperations: setupRls(PG_COMPANY_ID_RLS_CONFIG, companyId),
},
})
)
return prisma.$extends(extension)
}(I couldn't get why the |
|
It seems like my approch only works on the last prisma extension. return client.$transaction(async (tx) => {
setRLS(tx)
// we place our check results function in an interactive transaction
// so that our query is rolled back if check resul
return tx[model][op](args).then(checkResults)
})this part does not properly forward the query to the next extension... |
|
I do think this approach still has issues with batching in prisma. |
|
Thank you for this. Asking since documentation about seems quite obscure |
|
hi, i am not using this approach anymore. also i do not think it would be compatible with newest prisma client. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Question
( Prisma Client 5.14.0 )
Hi,
I have seen this topic too often - transactions within client extensions. It has been bothering me for RLS and now it is bothering me in development of my own client extension prisma-extension-casl
I have seen others like zenstack avoiding sequential transactions and only using interactive transactions. And this seems to be currently the only way to go.
Here is an example, where we check our queried results afterwards. And if it errors, it should revert the interactive transaction. There is also a RLS function. (I haven't tested it for the 'itx' case. Let me know if I should revise it)
I could collect a lot of issues relating to this, since I went through a lot...
This one was especially helpful: #20016 (reply in thread)
Some struggling with similar issues:
#17948
All reactions