@@ -12,6 +12,7 @@ import {
1212 NestedWriteVisitorContext ,
1313 enumerate ,
1414 getIdFields ,
15+ getModelInfo ,
1516 requireField ,
1617 resolveField ,
1718 type FieldInfo ,
@@ -435,17 +436,16 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
435436
436437 args = this . policyUtils . safeClone ( args ) ;
437438
438- // go through create items, statically check input to determine if post-create
439- // check is needed, and also validate zod schema
440- const needPostCreateCheck = this . validateCreateInput ( args ) ;
439+ // `createManyAndReturn` may need to be converted to regular `create`s
440+ const shouldConvertToCreate = this . preprocessCreateManyPayload ( args ) ;
441441
442- if ( ! needPostCreateCheck ) {
443- // direct create
442+ if ( ! shouldConvertToCreate ) {
443+ // direct `createMany`
444444 return this . modelClient . createMany ( args ) ;
445445 } else {
446446 // create entities in a transaction with post-create checks
447447 return this . queryUtils . transaction ( this . prisma , async ( tx ) => {
448- const { result, postWriteChecks } = await this . doCreateMany ( this . model , args , tx ) ;
448+ const { result, postWriteChecks } = await this . doCreateMany ( this . model , args , tx , 'createMany' ) ;
449449 // post-create check
450450 await this . runPostWriteChecks ( postWriteChecks , tx ) ;
451451 return { count : result . length } ;
@@ -472,14 +472,13 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
472472 const origArgs = args ;
473473 args = this . policyUtils . safeClone ( args ) ;
474474
475- // go through create items, statically check input to determine if post-create
476- // check is needed, and also validate zod schema
477- const needPostCreateCheck = this . validateCreateInput ( args ) ;
475+ // `createManyAndReturn` may need to be converted to regular `create`s
476+ const shouldConvertToCreate = this . preprocessCreateManyPayload ( args ) ;
478477
479478 let result : { result : unknown ; error ?: Error } [ ] ;
480479
481- if ( ! needPostCreateCheck ) {
482- // direct create
480+ if ( ! shouldConvertToCreate ) {
481+ // direct `createManyAndReturn`
483482 const created = await this . modelClient . createManyAndReturn ( args ) ;
484483
485484 // process read-back
@@ -489,7 +488,13 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
489488 } else {
490489 // create entities in a transaction with post-create checks
491490 result = await this . queryUtils . transaction ( this . prisma , async ( tx ) => {
492- const { result : created , postWriteChecks } = await this . doCreateMany ( this . model , args , tx ) ;
491+ const { result : created , postWriteChecks } = await this . doCreateMany (
492+ this . model ,
493+ args ,
494+ tx ,
495+ 'createManyAndReturn'
496+ ) ;
497+
493498 // post-create check
494499 await this . runPostWriteChecks ( postWriteChecks , tx ) ;
495500
@@ -510,6 +515,46 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
510515 } ) ;
511516 }
512517
518+ /**
519+ * Preprocess the payload of `createMany` and `createManyAndReturn` and update in place if needed.
520+ * @returns `true` if the operation should be converted to regular `create`s; false otherwise.
521+ */
522+ private preprocessCreateManyPayload ( args : { data : any ; select ?: any ; skipDuplicates ?: boolean } ) {
523+ if ( ! args ) {
524+ return false ;
525+ }
526+
527+ // if post-create check is needed
528+ const needPostCreateCheck = this . validateCreateInput ( args ) ;
529+
530+ // if the payload has any relation fields. Note that other enhancements (`withDefaultInAuth` for now)
531+ // can introduce relation fields into the payload
532+ let hasRelationFields = false ;
533+ if ( args . data ) {
534+ hasRelationFields = this . hasRelationFieldsInPayload ( this . model , args . data ) ;
535+ }
536+
537+ return needPostCreateCheck || hasRelationFields ;
538+ }
539+
540+ private hasRelationFieldsInPayload ( model : string , payload : any ) {
541+ const modelInfo = getModelInfo ( this . modelMeta , model ) ;
542+ if ( ! modelInfo ) {
543+ return false ;
544+ }
545+
546+ for ( const item of enumerate ( payload ) ) {
547+ for ( const field of Object . keys ( item ) ) {
548+ const fieldInfo = resolveField ( this . modelMeta , model , field ) ;
549+ if ( fieldInfo ?. isDataModel ) {
550+ return true ;
551+ }
552+ }
553+ }
554+
555+ return false ;
556+ }
557+
513558 private validateCreateInput ( args : { data : any ; skipDuplicates ?: boolean | undefined } ) {
514559 let needPostCreateCheck = false ;
515560 for ( const item of enumerate ( args . data ) ) {
@@ -537,7 +582,12 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
537582 return needPostCreateCheck ;
538583 }
539584
540- private async doCreateMany ( model : string , args : { data : any ; skipDuplicates ?: boolean } , db : CrudContract ) {
585+ private async doCreateMany (
586+ model : string ,
587+ args : { data : any ; skipDuplicates ?: boolean } ,
588+ db : CrudContract ,
589+ action : 'createMany' | 'createManyAndReturn'
590+ ) {
541591 // We can't call the native "createMany" because we can't get back what was created
542592 // for post-create checks. Instead, do a "create" for each item and collect the results.
543593
@@ -553,7 +603,7 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
553603 }
554604
555605 if ( this . shouldLogQuery ) {
556- this . logger . info ( `[policy] \`create\` for \`createMany \` ${ model } : ${ formatObject ( item ) } ` ) ;
606+ this . logger . info ( `[policy] \`create\` for \`${ action } \` ${ model } : ${ formatObject ( item ) } ` ) ;
557607 }
558608 return await db [ model ] . create ( { select : this . policyUtils . makeIdSelection ( model ) , data : item } ) ;
559609 } )
0 commit comments